From e1dbd2dca9e5ccf57ac2ea1c1e4ec8d46fcba8e2 Mon Sep 17 00:00:00 2001 From: worldmozara Date: Thu, 9 Jul 2026 20:07:37 +0800 Subject: [PATCH] feat/stubgen: add preliminary $Cxx.name support --- src/capnp_stubgen/gen_enum.py | 4 ++-- src/capnp_stubgen/gen_struct.py | 7 ++++--- src/capnp_stubgen/schema_walker.py | 6 +++--- src/capnp_stubgen/utils.py | 19 +++++++++++++++++++ tests/schemas/test_stubs.py | 2 +- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/capnp_stubgen/gen_enum.py b/src/capnp_stubgen/gen_enum.py index f561972..b32e945 100644 --- a/src/capnp_stubgen/gen_enum.py +++ b/src/capnp_stubgen/gen_enum.py @@ -7,7 +7,7 @@ from __future__ import annotations from typing import TYPE_CHECKING -from .utils import NodeDict +from .utils import NodeDict, get_cxx_name if TYPE_CHECKING: from .emitter import Emitter @@ -30,7 +30,7 @@ def generate_enum( literals: list[str] = [] for e in enumerants: - literals.append(f'"{e["name"]}"') + literals.append(f'"{get_cxx_name(e) or e["name"]}"') emitter.add_typing_import("Literal") line = f"Literal[{', '.join(literals)}]" diff --git a/src/capnp_stubgen/gen_struct.py b/src/capnp_stubgen/gen_struct.py index d7180c9..429165f 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 NodeDict, type_id_to_int +from .utils import NodeDict, get_cxx_name, type_id_to_int if TYPE_CHECKING: from .emitter import Emitter @@ -44,7 +44,7 @@ def generate_struct( # ── collect fields ───────────────────────────────────────────────── for field in struct_body.get("fields", []): - field_name: str = field["name"] + field_name: str = get_cxx_name(field) or field["name"] disc_value: int = field.get("discriminantValue", _NO_DISCRIMINANT) if "slot" in field: @@ -112,7 +112,8 @@ def generate_struct( generate_const(emitter, nt, registry) for field_name, type_str in non_union_fields: - emitter.add_field(field_name, type_str) + default = "None" if type_str == "None" else None + emitter.add_field(field_name, type_str, default=default) if discriminant_count > 0 and union_fields: _generate_union_methods(emitter, union_fields) diff --git a/src/capnp_stubgen/schema_walker.py b/src/capnp_stubgen/schema_walker.py index 7acd794..5349f88 100644 --- a/src/capnp_stubgen/schema_walker.py +++ b/src/capnp_stubgen/schema_walker.py @@ -7,7 +7,7 @@ building a ``TypeRegistry`` that maps type IDs to ``TypeInfo``. from __future__ import annotations from .models import TypeInfo, TypeRegistry -from .utils import NodeDict, get_display_name, get_node_kind, type_id_to_int +from .utils import NodeDict, get_cxx_name, get_display_name, get_node_kind, type_id_to_int _GENERATABLE_KINDS = frozenset({"struct", "enum", "interface", "const"}) @@ -35,7 +35,7 @@ def build_type_registry( continue type_id = type_id_to_int(node["id"]) - name = get_display_name(node) + name = get_cxx_name(node) or get_display_name(node) scope_id = node.get("scopeId", "0") parent_id = type_id_to_int(scope_id) if scope_id != "0" else None @@ -117,7 +117,7 @@ def _build_scoped_name( break if get_node_kind(node) != "file": - parts.append(get_display_name(node)) + parts.append(get_cxx_name(node) or get_display_name(node)) scope_id_str = node.get("scopeId", "0") scope_id = int(scope_id_str) if scope_id_str != "0" else 0 diff --git a/src/capnp_stubgen/utils.py b/src/capnp_stubgen/utils.py index 2faa683..a81df1b 100644 --- a/src/capnp_stubgen/utils.py +++ b/src/capnp_stubgen/utils.py @@ -38,3 +38,22 @@ def get_node_kind(node: NodeDict) -> str: def type_id_to_int(type_id: str) -> int: """Convert a type ID string (from dict) to an integer.""" return int(type_id) + + +# Annotation IDs from c++.capnp +_CXX_NAME_ANNOTATION_ID = 17466269397259751886 # 0xf2466592b7084e81 + + +def get_cxx_name(node: NodeDict) -> str | None: + """Return the ``$Cxx.name`` annotation value if present on a node.""" + try: + for annot in node.get("annotations", []): + if annot.get("id") == _CXX_NAME_ANNOTATION_ID: + val = annot.get("value") + if isinstance(val, dict): + text = val.get("text", "") + if isinstance(text, str): + return text + except Exception: + pass + return None diff --git a/tests/schemas/test_stubs.py b/tests/schemas/test_stubs.py index 7c729e6..de95a26 100644 --- a/tests/schemas/test_stubs.py +++ b/tests/schemas/test_stubs.py @@ -15,7 +15,7 @@ _HERE = Path(__file__).parent if str(_HERE) not in sys.path: sys.path.insert(0, str(_HERE)) -import capnp # pyright: ignore[reportMissingTypeStubs, reportUnusedImport] # activates import hook +import capnp # pyright: ignore[reportMissingTypeStubs] # activates import hook import test_generics_capnp import test_interface_capnp import test_nested_capnp