lint/stubgen: fix typing issues
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"]}"'
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
+14
-19
@@ -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"]))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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".
|
||||
|
||||
Reference in New Issue
Block a user