From 7156567302ec5252534e7845ae8a29d77abad63f Mon Sep 17 00:00:00 2001 From: worldmozara Date: Thu, 9 Jul 2026 15:54:59 +0800 Subject: [PATCH] lint/stubgen: fix typing issues --- src/capnp_stubgen/emitter.py | 2 +- src/capnp_stubgen/gen_const.py | 20 +++++++++--------- src/capnp_stubgen/gen_enum.py | 8 +++++--- src/capnp_stubgen/gen_generic.py | 6 ++++-- src/capnp_stubgen/gen_interface.py | 24 +++++++++++----------- src/capnp_stubgen/gen_struct.py | 14 ++++++------- src/capnp_stubgen/models.py | 6 +++++- src/capnp_stubgen/plugin.py | 33 +++++++++++++----------------- src/capnp_stubgen/schema_walker.py | 21 ++++++++----------- src/capnp_stubgen/type_resolver.py | 15 +++++--------- src/capnp_stubgen/utils.py | 19 ++++++++--------- 11 files changed, 80 insertions(+), 88 deletions(-) diff --git a/src/capnp_stubgen/emitter.py b/src/capnp_stubgen/emitter.py index 1c40191..70c90a3 100644 --- a/src/capnp_stubgen/emitter.py +++ b/src/capnp_stubgen/emitter.py @@ -25,7 +25,7 @@ class Emitter: """ def __init__(self, source_filename: str = "") -> None: - self._source_filename = source_filename + self._source_filename: str = source_filename self._header_lines: list[str] = [] self._body_lines: list[str] = [] self._indent_level: int = 0 diff --git a/src/capnp_stubgen/gen_const.py b/src/capnp_stubgen/gen_const.py index 5fa7bbf..fe05482 100644 --- a/src/capnp_stubgen/gen_const.py +++ b/src/capnp_stubgen/gen_const.py @@ -11,7 +11,7 @@ import math from typing import TYPE_CHECKING from .type_resolver import resolve_type -from .utils import type_id_to_int +from .utils import NodeDict, type_id_to_int if TYPE_CHECKING: from .emitter import Emitter @@ -31,10 +31,10 @@ def generate_const( globalText: str = "foobar" voidConst: None = None """ - node: dict = type_info.node - const_body: dict = node.get("const", {}) - type_dict: dict = const_body.get("type", {}) - value_dict: dict = const_body.get("value", {}) + node: NodeDict = type_info.node + const_body: NodeDict = node.get("const", {}) + type_dict: NodeDict = const_body.get("type", {}) + value_dict: NodeDict = const_body.get("value", {}) python_type_str = _resolve_const_type(type_dict, registry) python_value_str = _render_const_value(value_dict, type_dict, registry) @@ -49,7 +49,7 @@ def generate_const( # ── type resolution ──────────────────────────────────────────────────────── -def _resolve_const_type(type_dict: dict, registry: TypeRegistry) -> str: +def _resolve_const_type(type_dict: NodeDict, registry: TypeRegistry) -> str: """Resolve a constant's type to a Python type annotation string.""" # Handle enum references if "enum" in type_dict: @@ -83,8 +83,8 @@ def _resolve_const_type(type_dict: dict, registry: TypeRegistry) -> str: def _render_const_value( - value_dict: dict, - type_dict: dict, + value_dict: NodeDict, + type_dict: NodeDict, registry: TypeRegistry, ) -> str: """Render a constant's value as a Python literal.""" @@ -131,7 +131,7 @@ def _render_const_value( def _resolve_enum_value( - type_dict: dict, + type_dict: NodeDict, ordinal: int, registry: TypeRegistry, ) -> str: @@ -144,7 +144,7 @@ def _resolve_enum_value( if info is None: return repr(ordinal) - enum_node: dict = info.node + enum_node: NodeDict = info.node for e in enum_node.get("enum", {}).get("enumerants", []): if e.get("ordinal") == ordinal or e.get("codeOrder") == ordinal: return f'"{e["name"]}"' diff --git a/src/capnp_stubgen/gen_enum.py b/src/capnp_stubgen/gen_enum.py index f322436..940f0a9 100644 --- a/src/capnp_stubgen/gen_enum.py +++ b/src/capnp_stubgen/gen_enum.py @@ -7,6 +7,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from .utils import NodeDict + if TYPE_CHECKING: from .emitter import Emitter from .models import TypeInfo @@ -22,9 +24,9 @@ def generate_enum( Gender = Literal["male", "female", "other"] """ - node: dict = type_info.node - enum_body: dict = node.get("enum", {}) - enumerants: list[dict] = enum_body.get("enumerants", []) + node: NodeDict = type_info.node + enum_body: NodeDict = node.get("enum", {}) + enumerants: list[NodeDict] = enum_body.get("enumerants", []) literals: list[str] = [] for e in enumerants: diff --git a/src/capnp_stubgen/gen_generic.py b/src/capnp_stubgen/gen_generic.py index 8aa5538..af18459 100644 --- a/src/capnp_stubgen/gen_generic.py +++ b/src/capnp_stubgen/gen_generic.py @@ -8,6 +8,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from .utils import NodeDict + if TYPE_CHECKING: from .emitter import Emitter from .models import TypeInfo, TypeRegistry @@ -21,7 +23,7 @@ def generate_type_vars( Returns the list of TypeVar names for use in the ``Generic[...]`` base. """ - node: dict = type_info.node + node: NodeDict = type_info.node params = node.get("parameters", []) tv_names: list[str] = [] @@ -38,7 +40,7 @@ def generate_type_vars( def resolve_brand( - brand_dict: dict, + brand_dict: NodeDict, registry: TypeRegistry, ) -> list[str] | None: """Resolve brand bindings to Python type strings. diff --git a/src/capnp_stubgen/gen_interface.py b/src/capnp_stubgen/gen_interface.py index cae3244..0a1e6bf 100644 --- a/src/capnp_stubgen/gen_interface.py +++ b/src/capnp_stubgen/gen_interface.py @@ -14,7 +14,7 @@ from __future__ import annotations from typing import TYPE_CHECKING -from .utils import type_id_to_int +from .utils import NodeDict, type_id_to_int if TYPE_CHECKING: from .emitter import Emitter @@ -29,11 +29,11 @@ def fixup_interface_methods(registry: TypeRegistry) -> None: the interface by setting their ``parent_type_id`` and adjusting their ``scoped_name``. """ - for type_info in list(registry._types.values()): + for type_info in registry.all_types(): if type_info.kind != "interface": continue - node: dict = type_info.node + node: NodeDict = type_info.node for method in node.get("interface", {}).get("methods", []): mname = method["name"] param_id = type_id_to_int(method["paramStructType"]) @@ -47,7 +47,7 @@ def fixup_interface_methods(registry: TypeRegistry) -> None: f"{type_info.scoped_name}.{mname}.{suffix}" ) # Override name for class nesting - sinfo._display_name = ( + sinfo.display_name = ( f"{mname}{suffix}" ) @@ -58,13 +58,13 @@ def generate_interface( registry: TypeRegistry, ) -> None: """Generate interface classes for a Cap'n Proto interface.""" - node: dict = type_info.node - iface_body: dict = node.get("interface", {}) + node: NodeDict = type_info.node + iface_body: NodeDict = node.get("interface", {}) name = type_info.name - methods: list[dict] = iface_body.get("methods", []) + methods: list[NodeDict] = iface_body.get("methods", []) # Build method info list - method_infos: list[dict] = [] + method_infos: list[NodeDict] = [] for method in methods: param_id = type_id_to_int(method["paramStructType"]) result_id = type_id_to_int(method["resultStructType"]) @@ -129,22 +129,22 @@ def generate_interface( def _method_param_type( - mi: dict, + mi: NodeDict, iface_name: str, ) -> str: """Get the type string for a method's params.""" param_info = mi.get("param_info") if param_info is not None: - return f"{iface_name}.{param_info._display_name or param_info.name}Reader" + return f"{iface_name}.{param_info.display_name or param_info.name}Reader" return f"{iface_name}.{mi['name']}ParamsReader" def _method_result_type( - mi: dict, + mi: NodeDict, iface_name: str, ) -> str: """Get the type string for a method's results.""" result_info = mi.get("result_info") if result_info is not None: - return f"{iface_name}.{result_info._display_name or result_info.name}Reader" + return f"{iface_name}.{result_info.display_name or result_info.name}Reader" return f"{iface_name}.{mi['name']}ResultsReader" diff --git a/src/capnp_stubgen/gen_struct.py b/src/capnp_stubgen/gen_struct.py index 7600068..000ba20 100644 --- a/src/capnp_stubgen/gen_struct.py +++ b/src/capnp_stubgen/gen_struct.py @@ -16,7 +16,7 @@ from .gen_const import generate_const from .gen_enum import generate_enum from .gen_generic import generate_type_vars from .type_resolver import resolve_type -from .utils import type_id_to_int +from .utils import NodeDict, type_id_to_int if TYPE_CHECKING: from .emitter import Emitter @@ -32,9 +32,9 @@ def generate_struct( registry: TypeRegistry, ) -> None: """Generate the class triple for a Cap'n Proto struct.""" - node: dict = type_info.node - struct_body: dict = node.get("struct", {}) - name = type_info._display_name or type_info.name + node: NodeDict = type_info.node + struct_body: NodeDict = node.get("struct", {}) + name = type_info.display_name or type_info.name discriminant_count = struct_body.get("discriminantCount", 0) nested_types = registry.get_children(type_info.type_id) @@ -138,7 +138,7 @@ def generate_struct( def _resolve_field_type( - type_dict: dict, registry: TypeRegistry, emitter: Emitter, + type_dict: NodeDict, registry: TypeRegistry, emitter: Emitter, ) -> str | None: """Resolve a field's type to a Python type annotation string.""" which = _type_which(type_dict) @@ -159,7 +159,7 @@ def _resolve_field_type( def _resolve_list_field_type( - list_dict: dict, registry: TypeRegistry, emitter: Emitter, + list_dict: NodeDict, registry: TypeRegistry, emitter: Emitter, ) -> str: """Resolve a ``List(T)`` field type.""" emitter.add_typing_import("Sequence", "collections.abc") @@ -195,7 +195,7 @@ def _resolve_list_field_type( return result -def _type_which(type_dict: dict) -> str | None: +def _type_which(type_dict: NodeDict) -> str | None: """Return the union variant key for a type dict.""" for key in ("void", "bool", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", diff --git a/src/capnp_stubgen/models.py b/src/capnp_stubgen/models.py index a4ed8c3..263a0eb 100644 --- a/src/capnp_stubgen/models.py +++ b/src/capnp_stubgen/models.py @@ -109,7 +109,7 @@ class TypeInfo: """The ID of the file node that contains this type (for import resolution).""" # May be overridden for method param/result structs - _display_name: str | None = None + display_name: str | None = None """Override for the display name (used for method param/result structs).""" @@ -172,5 +172,9 @@ class TypeRegistry: def __len__(self) -> int: return len(self._types) + def all_types(self) -> list[TypeInfo]: + """Return all registered types (for iteration/filtering).""" + return list(self._types.values()) + def __contains__(self, type_id: int) -> bool: return type_id in self._types diff --git a/src/capnp_stubgen/plugin.py b/src/capnp_stubgen/plugin.py index 06a4c12..bcb8024 100644 --- a/src/capnp_stubgen/plugin.py +++ b/src/capnp_stubgen/plugin.py @@ -13,19 +13,16 @@ from __future__ import annotations import os import sys -from typing import TYPE_CHECKING +from typing import Any from .emitter import Emitter from .gen_const import generate_const from .gen_enum import generate_enum from .gen_interface import fixup_interface_methods, generate_interface from .gen_struct import generate_struct -from .models import TypeRegistry +from .models import TypeInfo, TypeRegistry from .schema_walker import build_type_registry -from .utils import filename_to_module_name, type_id_to_int - -if TYPE_CHECKING: - from typing import Any +from .utils import NodeDict, filename_to_module_name, type_id_to_int def main() -> int: @@ -38,15 +35,13 @@ def main() -> int: def _main_impl() -> int: - import capnp - # ── 1. Read CodeGeneratorRequest from stdin ──────────────────────── request = _read_request() # Convert to dict for processing (avoids C++ DynamicStruct quirks) request_dict = request.to_dict() - nodes: list[dict] = request_dict["nodes"] - requested_files: list[dict] = request_dict["requestedFiles"] + nodes: list[NodeDict] = request_dict["nodes"] + requested_files: list[NodeDict] = request_dict["requestedFiles"] # ── 2. Build type registry ───────────────────────────────────────── registry = build_type_registry(nodes, requested_files) @@ -79,7 +74,7 @@ def _main_impl() -> int: output_path = f"{module_name}.pyi" with open(output_path, "w") as f: - f.write(content) + _ = f.write(content) except Exception as exc: print( @@ -100,13 +95,13 @@ def _read_request() -> Any: Uses pycapnp's ``SchemaParser`` to dynamically load ``schema.capnp`` (shipped with pycapnp), then casts the stdin message. """ - import capnp + import capnp # pyright: ignore[reportMissingTypeStubs] capnp_dir = os.path.dirname(capnp.__file__) schema_path = os.path.join(capnp_dir, "schema.capnp") import_base = os.path.dirname(capnp_dir) - parser = capnp.SchemaParser() + parser = capnp.SchemaParser() # pyright: ignore[reportAttributeAccessIssue] schema_module = parser.load(schema_path, imports=[import_base]) return schema_module.CodeGeneratorRequest.read(sys.stdin.buffer) @@ -130,7 +125,7 @@ def _generate_file( _ORDER = {"enum": 0, "struct": 1, "const": 2, "interface": 3} - def _sort_key(info): + def _sort_key(info: TypeInfo) -> tuple[int, str]: return (_ORDER.get(info.kind, 99), info.name) top_level.sort(key=_sort_key) @@ -163,12 +158,12 @@ def _resolve_cross_imports( # Collect all types belonging to this file own_types: set[int] = set() - for info in registry._types.values(): + for info in registry.all_types(): if info.file_id == file_id and info.kind in ("struct", "enum", "interface"): own_types.add(info.type_id) # Walk types and find external references - for info in registry._types.values(): + for info in registry.all_types(): if info.file_id != file_id: continue @@ -196,7 +191,7 @@ def _resolve_cross_imports( all_imports: list[str] = [] for tname in sorted(type_names): ref_info = None - for info in registry._types.values(): + for info in registry.all_types(): if info.scoped_name == tname and info.file_id != file_id: ref_info = info break @@ -226,7 +221,7 @@ def _collect_type_references(info: TypeInfo, registry: TypeRegistry) -> set[int] return refs -def _collect_field_references(field: dict, refs: set[int], registry: TypeRegistry) -> None: +def _collect_field_references(field: NodeDict, refs: set[int], _registry: TypeRegistry) -> None: """Recursively collect type IDs from a field's type.""" if "slot" in field: _collect_type_references_from_dict(field["slot"].get("type", {}), refs) @@ -234,7 +229,7 @@ def _collect_field_references(field: dict, refs: set[int], registry: TypeRegistr refs.add(type_id_to_int(field["group"]["typeId"])) -def _collect_type_references_from_dict(type_dict: dict, refs: set[int]) -> None: +def _collect_type_references_from_dict(type_dict: NodeDict, refs: set[int]) -> None: """Recursively collect type IDs from a type dict.""" if "struct" in type_dict: refs.add(type_id_to_int(type_dict["struct"]["typeId"])) diff --git a/src/capnp_stubgen/schema_walker.py b/src/capnp_stubgen/schema_walker.py index 1dcc03a..7acd794 100644 --- a/src/capnp_stubgen/schema_walker.py +++ b/src/capnp_stubgen/schema_walker.py @@ -6,27 +6,22 @@ building a ``TypeRegistry`` that maps type IDs to ``TypeInfo``. from __future__ import annotations -from typing import TYPE_CHECKING - from .models import TypeInfo, TypeRegistry -from .utils import get_display_name, get_node_kind, type_id_to_int - -if TYPE_CHECKING: - from typing import Any +from .utils import NodeDict, get_display_name, get_node_kind, type_id_to_int _GENERATABLE_KINDS = frozenset({"struct", "enum", "interface", "const"}) def build_type_registry( - nodes: list[dict], - requested_files: list[dict], + nodes: list[NodeDict], + requested_files: list[NodeDict], ) -> TypeRegistry: """Build a ``TypeRegistry`` from the to-dict CodeGeneratorRequest nodes.""" registry = TypeRegistry() # Build ID → node lookup - nodes_by_id: dict[int, dict] = {} + nodes_by_id: dict[int, NodeDict] = {} for node in nodes: nodes_by_id[type_id_to_int(node["id"])] = node @@ -71,8 +66,8 @@ def build_type_registry( def _build_file_associations( - nodes_by_id: dict[int, dict], - requested_files: list[dict], + nodes_by_id: dict[int, NodeDict], + _requested_files: list[NodeDict], registry: TypeRegistry, ) -> None: for node_id in nodes_by_id: @@ -82,7 +77,7 @@ def _build_file_associations( def _find_containing_file( - node_id: int, nodes_by_id: dict[int, dict] + node_id: int, nodes_by_id: dict[int, NodeDict] ) -> int | None: visited: set[int] = set() current_id = node_id @@ -109,7 +104,7 @@ def _find_containing_file( def _build_scoped_name( - type_id: int, nodes_by_id: dict[int, dict] + type_id: int, nodes_by_id: dict[int, NodeDict] ) -> str: parts: list[str] = [] visited: set[int] = set() diff --git a/src/capnp_stubgen/type_resolver.py b/src/capnp_stubgen/type_resolver.py index 1b866f5..9131bdb 100644 --- a/src/capnp_stubgen/type_resolver.py +++ b/src/capnp_stubgen/type_resolver.py @@ -5,13 +5,8 @@ Maps type dicts (from ``CodeGeneratorRequest.to_dict()``) to ``PythonType``. from __future__ import annotations -from typing import TYPE_CHECKING - from .models import PythonType, TypeRegistry -from .utils import type_id_to_int - -if TYPE_CHECKING: - from typing import Any +from .utils import NodeDict, type_id_to_int # Cap'n Proto type key → Python type name @@ -34,7 +29,7 @@ CAPNP_TO_PYTHON: dict[str, str] = { def resolve_type( - type_dict: dict, + type_dict: NodeDict, registry: TypeRegistry, ) -> PythonType | None: """Resolve a type dict to a ``PythonType``. @@ -84,7 +79,7 @@ def resolve_type( return PythonType(name="Any") -def _type_which(type_dict: dict) -> str | None: +def _type_which(type_dict: NodeDict) -> str | None: """Return the union variant key for a type dict.""" for key in CAPNP_TO_PYTHON: if key in type_dict: @@ -95,7 +90,7 @@ def _type_which(type_dict: dict) -> str | None: return None -def _resolve_list_type(list_dict: dict, registry: TypeRegistry) -> PythonType: +def _resolve_list_type(list_dict: NodeDict, registry: TypeRegistry) -> PythonType: """Resolve a List(T) type.""" depth = 1 inner = list_dict.get("elementType", {}) @@ -129,7 +124,7 @@ def _resolve_named_type(type_id: int, registry: TypeRegistry) -> PythonType: def _resolve_any_pointer( - ap_dict: dict, registry: TypeRegistry + ap_dict: NodeDict, registry: TypeRegistry ) -> PythonType | None: """Resolve an AnyPointer type.""" if "unconstrained" in ap_dict: diff --git a/src/capnp_stubgen/utils.py b/src/capnp_stubgen/utils.py index 013ec9e..2faa683 100644 --- a/src/capnp_stubgen/utils.py +++ b/src/capnp_stubgen/utils.py @@ -1,6 +1,11 @@ """Utility functions for capnp-stubgen.""" import os +from typing import Any + +#: A dict from capnp's ``CodeGeneratorRequest.to_dict()``. +#: Keys are strings, values are arbitrary nested capnp data. +NodeDict = dict[str, Any] def filename_to_module_name(filename: str) -> str: @@ -14,18 +19,12 @@ def filename_to_module_name(filename: str) -> str: return base + "_capnp" -def get_display_name(node: dict) -> str: - """Extract the short display name from a node dict. - - The node's ``displayName`` field contains a path like - ``path/to/file.capnp:TypeName``. This removes the file prefix. - """ - name: str = node["displayName"] - prefix_len: int = node["displayNamePrefixLength"] - return name[prefix_len:] +def get_display_name(node: NodeDict) -> str: + """Extract the short display name from a node dict.""" + return str(node["displayName"])[int(node["displayNamePrefixLength"]):] -def get_node_kind(node: dict) -> str: +def get_node_kind(node: NodeDict) -> str: """Determine the kind of a node from its dict representation. Returns one of: "file", "struct", "enum", "interface", "const", "annotation".