From a79cd1550c0f9deeb69a663835ff0a58557b3e96 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 7 Sep 2026 12:33:52 +0200 Subject: [PATCH] [api] Collapse the generator's per wire type decode hooks into one A type now sets a single decode_expr; the wire type it already declares picks the case label. Drops the unused force_str helper, two dead decode_length overrides, a duplicate field builder in the generator tests and a needless list copy in the state waiter. The generated files are unchanged. --- script/api_protobuf/api_protobuf.py | 67 ++++++------------- .../api/test_api_protobuf_generator.py | 3 +- 2 files changed, 21 insertions(+), 49 deletions(-) diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 6ce9344b67..1b99fd0b8b 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -131,11 +131,6 @@ def camel_to_snake(name: str) -> str: return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() -def force_str(force: bool) -> str: - """Convert a boolean force value to string format for C++ code.""" - return str(force).lower() - - def _encode_call(func: str, *args: str, force: bool = False) -> str: """Emit one ProtoEncode call; every helper takes the cursor and returns it advanced.""" suffix = "_force" if force else "" @@ -240,20 +235,8 @@ class TypeInfo(ABC): f"{body}\nbreak;" ) - # Decode expression per wire type; a decodable type sets exactly one. - decode_varint = None - decode_length = None - decode_32bit = None - - def _decode_expr(self) -> str | None: - return next( - ( - expr - for expr in (self.decode_varint, self.decode_length, self.decode_32bit) - if expr is not None - ), - None, - ) + # Expression that reads this field from `value`; None when the type is never decoded. + decode_expr: str | None = None def _decode_store(self, expr: str) -> str: return f"this->{self.field_name} = {expr};" @@ -261,7 +244,7 @@ class TypeInfo(ABC): @property def decode_content(self) -> str | None: """The decode_field() case for this field, or None when it is never decoded.""" - expr = self._decode_expr() + expr = self.decode_expr return None if expr is None else self.decode_case(self._decode_store(expr)) # Mapping from encode_func to raw encode expression template. @@ -662,7 +645,7 @@ class DoubleType(FixedSizeTypeMixin, TypeInfo): class FloatType(FixedSizeTypeMixin, TypeInfo): cpp_type = "float" default_value = "0.0f" - decode_32bit = "value.as_float()" + decode_expr = "value.as_float()" encode_func = "encode_float" wire_type = WireType.FIXED32 # Uses wire type 5 @@ -691,7 +674,7 @@ class Int64Type(VarintTypeMixin, TypeInfo): cpp_type = "int64_t" _varint_max_bits = 64 default_value = "0" - decode_varint = "static_cast(value.as_varint())" + decode_expr = "static_cast(value.as_varint())" encode_func = "encode_int64" wire_type = WireType.VARINT # Uses wire type 0 @@ -712,7 +695,7 @@ class UInt64Type(VarintTypeMixin, TypeInfo): cpp_type = "uint64_t" _varint_max_bits = 64 default_value = "0" - decode_varint = "value.as_varint()" + decode_expr = "value.as_varint()" encode_func = "encode_uint64" wire_type = WireType.VARINT # Uses wire type 0 @@ -747,7 +730,7 @@ class Int32Type(VarintTypeMixin, TypeInfo): cpp_type = "int32_t" _varint_max_bits = 64 # int32 is sign-extended to 64 bits in protobuf default_value = "0" - decode_varint = "static_cast(value.as_varint())" + decode_expr = "static_cast(value.as_varint())" encode_func = "encode_int32" wire_type = WireType.VARINT # Uses wire type 0 @@ -792,7 +775,7 @@ class Fixed64Type(FixedSizeTypeMixin, TypeInfo): class Fixed32Type(FixedSizeTypeMixin, TypeInfo): cpp_type = "uint32_t" default_value = "0" - decode_32bit = "value.as_fixed32()" + decode_expr = "value.as_fixed32()" encode_func = "encode_fixed32" wire_type = WireType.FIXED32 # Uses wire type 5 @@ -821,7 +804,7 @@ class BoolType(VarintTypeMixin, TypeInfo): _varint_max_bits = 1 cpp_type = "bool" default_value = "false" - decode_varint = "value.as_varint() != 0" + decode_expr = "value.as_varint() != 0" encode_func = "encode_bool" wire_type = WireType.VARINT # Uses wire type 0 @@ -841,7 +824,7 @@ class StringType(TypeInfo): default_value = "" reference_type = "std::string &" const_reference_type = "const std::string &" - decode_length = "value.as_string()" + decode_expr = "value.as_string()" encode_func = "encode_string" wire_type = WireType.LENGTH_DELIMITED # Uses wire type 2 @@ -982,14 +965,6 @@ class MessageType(TypeInfo): self.encode_func, "buffer", str(self.number), f"this->{self.field_name}" ) - @property - def decode_length(self) -> str: - # Override to return None for message types because we can't use template-based - # decoding when the specific message type isn't known at compile time. - # Instead, we use the non-template decode_to_message() method which allows - # runtime polymorphism through virtual function calls. - return None - @property def public_content(self) -> list[str]: content = [self.class_member] @@ -1052,7 +1027,7 @@ class BytesType(TypeInfo): reference_type = "std::string &" const_reference_type = "const std::string &" encode_func = "encode_bytes" - decode_length = "value.as_string()" + decode_expr = "value.as_string()" wire_type = WireType.LENGTH_DELIMITED # Uses wire type 2 @property @@ -1162,11 +1137,6 @@ class PointerToBufferTypeBase(TypeInfo): super().__init__(field) self.array_size = 0 - @property - def decode_length(self) -> str | None: - # This is handled in decode_content - return None - @property def wire_type(self) -> WireType: """Get the wire type for this field.""" @@ -1502,7 +1472,7 @@ class UInt32Type(VarintTypeMixin, TypeInfo): cpp_type = "uint32_t" _varint_max_bits = 32 default_value = "0" - decode_varint = "value.as_varint()" + decode_expr = "value.as_varint()" encode_func = "encode_uint32" wire_type = WireType.VARINT # Uses wire type 0 @@ -1530,7 +1500,7 @@ class EnumType(VarintTypeMixin, TypeInfo): return f"enums::{self._field.type_name[1:]}" @property - def decode_varint(self) -> str: + def decode_expr(self) -> str: return f"static_cast<{self.cpp_type}>(value.as_varint())" default_value = "" @@ -1578,7 +1548,7 @@ class EnumType(VarintTypeMixin, TypeInfo): class SFixed32Type(FixedSizeTypeMixin, TypeInfo): cpp_type = "int32_t" default_value = "0" - decode_32bit = "value.as_sfixed32()" + decode_expr = "value.as_sfixed32()" encode_func = "encode_sfixed32" wire_type = WireType.FIXED32 # Uses wire type 5 @@ -1630,7 +1600,7 @@ class SInt32Type(VarintTypeMixin, TypeInfo): cpp_type = "int32_t" _varint_max_bits = 32 # zigzag encoding keeps it 32-bit default_value = "0" - decode_varint = "decode_zigzag32(static_cast(value.as_varint()))" + decode_expr = "decode_zigzag32(static_cast(value.as_varint()))" encode_func = "encode_sint32" wire_type = WireType.VARINT # Uses wire type 0 @@ -1651,7 +1621,7 @@ class SInt64Type(VarintTypeMixin, TypeInfo): cpp_type = "int64_t" _varint_max_bits = 64 default_value = "0" - decode_varint = "decode_zigzag64(value.as_varint())" + decode_expr = "decode_zigzag64(value.as_varint())" encode_func = "encode_sint64" wire_type = WireType.VARINT # Uses wire type 0 @@ -2113,8 +2083,9 @@ class RepeatedTypeInfo(TypeInfo): """ return self._ti.wire_type - def _decode_expr(self) -> str | None: - return self._ti._decode_expr() + @property + def decode_expr(self) -> str | None: + return self._ti.decode_expr def _decode_store(self, expr: str) -> str: return f"this->{self.field_name}.push_back({expr});" diff --git a/tests/unit_tests/components/api/test_api_protobuf_generator.py b/tests/unit_tests/components/api/test_api_protobuf_generator.py index 7a69478710..accffc755f 100644 --- a/tests/unit_tests/components/api/test_api_protobuf_generator.py +++ b/tests/unit_tests/components/api/test_api_protobuf_generator.py @@ -38,7 +38,8 @@ def _file_with_messages( file_desc = descriptor_pb2.FileDescriptorProto(name="test.proto") for name, field_type, deprecated in messages: msg = file_desc.message_type.add(name=name) - field = msg.field.add(name="value", number=1, type=field_type) + field = msg.field.add() + field.CopyFrom(_field(field_type)) field.options.deprecated = deprecated return file_desc