diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 1a491308c86..f454a86a322 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -377,7 +377,7 @@ class InfoResponseProtoMessage : public ProtoMessage { StringRef name{}; bool disabled_by_default{false}; #ifdef USE_ENTITY_ICON - StringRef icon{nullptr, 0}; + StringRef icon{nullptr, 0}; // null until set, encode only #endif enums::EntityCategory entity_category{}; #ifdef USE_DEVICES @@ -549,7 +549,7 @@ class DeviceInfo final : public ProtoMessage { #ifdef USE_SERIAL_PROXY class SerialProxyInfo final : public ProtoMessage { public: - StringRef name{nullptr, 0}; + StringRef name{nullptr, 0}; // null until set, encode only enums::SerialProxyPortType port_type{}; uint32_t configured_line_states{0}; static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); @@ -744,7 +744,7 @@ class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("list_entities_binary_sensor_response"); } #endif - StringRef device_class{nullptr, 0}; + StringRef device_class{nullptr, 0}; // null until set, encode only bool is_status_binary_sensor{false}; static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { @@ -791,7 +791,7 @@ class ListEntitiesCoverResponse final : public InfoResponseProtoMessage { bool assumed_state{false}; bool supports_position{false}; bool supports_tilt{false}; - StringRef device_class{nullptr, 0}; + StringRef device_class{nullptr, 0}; // null until set, encode only bool supports_stop{false}; static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { @@ -883,7 +883,7 @@ class FanStateResponse final : public StateResponseProtoMessage { bool oscillating{false}; enums::FanDirection direction{}; int32_t speed_level{0}; - StringRef preset_mode{nullptr, 0}; + StringRef preset_mode{nullptr, 0}; // null until set, encode only static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -963,7 +963,7 @@ class LightStateResponse final : public StateResponseProtoMessage { float color_temperature{0.0f}; float cold_white{0.0f}; float warm_white{0.0f}; - StringRef effect{nullptr, 0}; + StringRef effect{nullptr, 0}; // null until set, encode only static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -1025,10 +1025,10 @@ class ListEntitiesSensorResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("list_entities_sensor_response"); } #endif - StringRef unit_of_measurement{nullptr, 0}; + StringRef unit_of_measurement{nullptr, 0}; // null until set, encode only int32_t accuracy_decimals{0}; bool force_update{false}; - StringRef device_class{nullptr, 0}; + StringRef device_class{nullptr, 0}; // null until set, encode only enums::SensorStateClass state_class{}; static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { @@ -1073,7 +1073,7 @@ class ListEntitiesSwitchResponse final : public InfoResponseProtoMessage { const LogString *message_name() const override { return LOG_STR("list_entities_switch_response"); } #endif bool assumed_state{false}; - StringRef device_class{nullptr, 0}; + StringRef device_class{nullptr, 0}; // null until set, encode only static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -1130,7 +1130,7 @@ class ListEntitiesTextSensorResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("list_entities_text_sensor_response"); } #endif - StringRef device_class{nullptr, 0}; + StringRef device_class{nullptr, 0}; // null until set, encode only static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -1150,7 +1150,7 @@ class TextSensorStateResponse final : public StateResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("text_sensor_state_response"); } #endif - StringRef state{nullptr, 0}; + StringRef state{nullptr, 0}; // null until set, encode only bool missing_state{false}; static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { @@ -1248,8 +1248,8 @@ class NoiseEncryptionSetKeyResponse final : public ProtoMessage { #ifdef USE_API_HOMEASSISTANT_SERVICES class HomeassistantServiceMap final : public ProtoMessage { public: - StringRef key{nullptr, 0}; - StringRef value{nullptr, 0}; + StringRef key{nullptr, 0}; // null until set, encode only + StringRef value{nullptr, 0}; // null until set, encode only static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -1269,7 +1269,7 @@ class HomeassistantActionRequest final : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("homeassistant_action_request"); } #endif - StringRef service{nullptr, 0}; + StringRef service{nullptr, 0}; // null until set, encode only FixedVector data{}; FixedVector data_template{}; FixedVector variables{}; @@ -1281,7 +1281,7 @@ class HomeassistantActionRequest final : public ProtoMessage { bool wants_response{false}; #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - StringRef response_template{nullptr, 0}; + StringRef response_template{nullptr, 0}; // null until set, encode only #endif static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { @@ -1327,8 +1327,8 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("subscribe_home_assistant_state_response"); } #endif - StringRef entity_id{nullptr, 0}; - StringRef attribute{nullptr, 0}; + StringRef entity_id{nullptr, 0}; // null until set, encode only + StringRef attribute{nullptr, 0}; // null until set, encode only bool once{false}; static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { @@ -1421,13 +1421,13 @@ class GetTimeResponse final : public ProtoDecodableMessage { #ifdef USE_API_USER_DEFINED_ACTIONS class ListEntitiesServicesArgument final : public ProtoMessage { public: - StringRef name{nullptr, 0}; + StringRef name{nullptr, 0}; // null until set, encode only enums::ServiceArgType type{}; #ifdef USE_API_USER_DEFINED_ACTION_METADATA - StringRef description{nullptr, 0}; + StringRef description{nullptr, 0}; // null until set, encode only #endif #ifdef USE_API_USER_DEFINED_ACTION_METADATA - StringRef example{nullptr, 0}; + StringRef example{nullptr, 0}; // null until set, encode only #endif static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { @@ -1448,12 +1448,12 @@ class ListEntitiesServicesResponse final : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("list_entities_services_response"); } #endif - StringRef name{nullptr, 0}; + StringRef name{nullptr, 0}; // null until set, encode only uint32_t key{0}; FixedVector args{}; enums::SupportsResponseType supports_response{}; #ifdef USE_API_USER_DEFINED_ACTION_METADATA - StringRef description{nullptr, 0}; + StringRef description{nullptr, 0}; // null until set, encode only #endif static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { @@ -1520,7 +1520,7 @@ class ExecuteServiceResponse final : public ProtoMessage { #endif uint32_t call_id{0}; bool success{false}; - StringRef error_message{nullptr, 0}; + StringRef error_message{nullptr, 0}; // null until set, encode only #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON const uint8_t *response_data{nullptr}; uint16_t response_data_len{0}; @@ -1655,9 +1655,9 @@ class ClimateStateResponse final : public StateResponseProtoMessage { enums::ClimateAction action{}; enums::ClimateFanMode fan_mode{}; enums::ClimateSwingMode swing_mode{}; - StringRef custom_fan_mode{nullptr, 0}; + StringRef custom_fan_mode{nullptr, 0}; // null until set, encode only enums::ClimatePreset preset{}; - StringRef custom_preset{nullptr, 0}; + StringRef custom_preset{nullptr, 0}; // null until set, encode only float current_humidity{0.0f}; float target_humidity{0.0f}; static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); @@ -1790,9 +1790,9 @@ class ListEntitiesNumberResponse final : public InfoResponseProtoMessage { float min_value{0.0f}; float max_value{0.0f}; float step{0.0f}; - StringRef unit_of_measurement{nullptr, 0}; + StringRef unit_of_measurement{nullptr, 0}; // null until set, encode only enums::NumberMode mode{}; - StringRef device_class{nullptr, 0}; + StringRef device_class{nullptr, 0}; // null until set, encode only static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -1870,7 +1870,7 @@ class SelectStateResponse final : public StateResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("select_state_response"); } #endif - StringRef state{nullptr, 0}; + StringRef state{nullptr, 0}; // null until set, encode only bool missing_state{false}; static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { @@ -1977,7 +1977,7 @@ class ListEntitiesLockResponse final : public InfoResponseProtoMessage { bool assumed_state{false}; bool supports_open{false}; bool requires_code{false}; - StringRef code_format{nullptr, 0}; + StringRef code_format{nullptr, 0}; // null until set, encode only static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -2036,7 +2036,7 @@ class ListEntitiesButtonResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("list_entities_button_response"); } #endif - StringRef device_class{nullptr, 0}; + StringRef device_class{nullptr, 0}; // null until set, encode only static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -2067,7 +2067,7 @@ class ButtonCommandRequest final : public CommandProtoMessage { #ifdef USE_MEDIA_PLAYER class MediaPlayerSupportedFormat final : public ProtoMessage { public: - StringRef format{nullptr, 0}; + StringRef format{nullptr, 0}; // null until set, encode only uint32_t sample_rate{0}; uint32_t num_channels{0}; enums::MediaPlayerFormatPurpose purpose{}; @@ -2727,10 +2727,10 @@ class VoiceAssistantRequest final : public ProtoMessage { const LogString *message_name() const override { return LOG_STR("voice_assistant_request"); } #endif bool start{false}; - StringRef conversation_id{nullptr, 0}; + StringRef conversation_id{nullptr, 0}; // null until set, encode only uint32_t flags{0}; VoiceAssistantAudioSettings audio_settings{}; - StringRef wake_word_phrase{nullptr, 0}; + StringRef wake_word_phrase{nullptr, 0}; // null until set, encode only static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -2871,8 +2871,8 @@ class VoiceAssistantAnnounceFinished final : public ProtoMessage { }; class VoiceAssistantWakeWord final : public ProtoMessage { public: - StringRef id{nullptr, 0}; - StringRef wake_word{nullptr, 0}; + StringRef id{nullptr, 0}; // null until set, encode only + StringRef wake_word{nullptr, 0}; // null until set, encode only std::vector trained_languages{}; static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { @@ -3025,7 +3025,7 @@ class ListEntitiesTextResponse final : public InfoResponseProtoMessage { #endif uint32_t min_length{0}; uint32_t max_length{0}; - StringRef pattern{nullptr, 0}; + StringRef pattern{nullptr, 0}; // null until set, encode only enums::TextMode mode{}; static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { @@ -3046,7 +3046,7 @@ class TextStateResponse final : public StateResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("text_state_response"); } #endif - StringRef state{nullptr, 0}; + StringRef state{nullptr, 0}; // null until set, encode only bool missing_state{false}; static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { @@ -3206,7 +3206,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("list_entities_event_response"); } #endif - StringRef device_class{nullptr, 0}; + StringRef device_class{nullptr, 0}; // null until set, encode only const FixedVector *event_types{}; static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { @@ -3227,7 +3227,7 @@ class EventResponse final : public StateResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("event_response"); } #endif - StringRef event_type{nullptr, 0}; + StringRef event_type{nullptr, 0}; // null until set, encode only static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -3249,7 +3249,7 @@ class ListEntitiesValveResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("list_entities_valve_response"); } #endif - StringRef device_class{nullptr, 0}; + StringRef device_class{nullptr, 0}; // null until set, encode only bool assumed_state{false}; bool supports_position{false}; bool supports_stop{false}; @@ -3369,7 +3369,7 @@ class ListEntitiesUpdateResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("list_entities_update_response"); } #endif - StringRef device_class{nullptr, 0}; + StringRef device_class{nullptr, 0}; // null until set, encode only static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -3393,11 +3393,11 @@ class UpdateStateResponse final : public StateResponseProtoMessage { bool in_progress{false}; bool has_progress{false}; float progress{0.0f}; - StringRef current_version{nullptr, 0}; - StringRef latest_version{nullptr, 0}; - StringRef title{nullptr, 0}; - StringRef release_summary{nullptr, 0}; - StringRef release_url{nullptr, 0}; + StringRef current_version{nullptr, 0}; // null until set, encode only + StringRef latest_version{nullptr, 0}; // null until set, encode only + StringRef title{nullptr, 0}; // null until set, encode only + StringRef release_summary{nullptr, 0}; // null until set, encode only + StringRef release_url{nullptr, 0}; // null until set, encode only static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); @@ -3735,7 +3735,7 @@ class SerialProxyRequestResponse final : public ProtoMessage { uint32_t instance{0}; enums::SerialProxyRequestType type{}; enums::SerialProxyStatus status{}; - StringRef error_message{nullptr, 0}; + StringRef error_message{nullptr, 0}; // null until set, encode only static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 70942d97f85..09d0ea3cc98 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -566,11 +566,11 @@ def create_field_type_info( # For messages that decode (SOURCE_CLIENT or SOURCE_BOTH), use pointer # for zero-copy access to the receive buffer if needs_decode: - return PointerToBytesBufferType(field, None) + return PointerToBytesBufferType(field) # For SOURCE_SERVER (encode only), explicit annotation is still needed if get_field_opt(field, pb.pointer_to_buffer, False): - return PointerToBytesBufferType(field, None) + return PointerToBytesBufferType(field) return BytesType(field, needs_decode, needs_encode) @@ -1135,9 +1135,9 @@ class PointerToBufferTypeBase(TypeInfo): return False def __init__( - self, field: descriptor.FieldDescriptorProto, size: int | None = None + self, field: descriptor.FieldDescriptorProto, needs_decode: bool = True ) -> None: - super().__init__(field) + super().__init__(field, needs_decode) self.array_size = 0 @property @@ -1215,28 +1215,32 @@ class PointerToStringBufferType(PointerToBufferTypeBase): reference_type = "StringRef &" const_reference_type = "const StringRef &" - def __init__( - self, field: descriptor.FieldDescriptorProto, needs_decode: bool - ) -> None: - super().__init__(field, None) - self._needs_decode = needs_decode - @classmethod def can_use_dump_field(cls) -> bool: return True + @property + def _starts_null(self) -> bool: + """A field that is only encoded, and skipped when empty, never has its pointer read + before it is set, so it can default to a null StringRef and the message constructs as + one zero fill. Any encode path that copies unconditionally must check this.""" + return not self._needs_decode and not self.force + @property def public_content(self) -> list[str]: - # A field that is only ever encoded and skipped when empty never has its pointer read, so a - # null default lets the whole message construct as one zero fill - if not self._needs_decode and not self.force: - return [f"StringRef {self.field_name}{{nullptr, 0}};"] + if self._starts_null: + return [ + f"StringRef {self.field_name}{{nullptr, 0}}; // null until set, encode only" + ] return [f"StringRef {self.field_name}{{}};"] @property def encode_content(self) -> str: max_len = self.max_data_length if max_len is not None and max_len < 128 and self.force: + assert not self._starts_null, ( + "unconditional copy of a field that may start null" + ) tag = self.calculate_tag() if tag < 128: return _encode_call( 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 600b4330d5a..d3b3adb3b94 100644 --- a/tests/unit_tests/components/api/test_api_protobuf_generator.py +++ b/tests/unit_tests/components/api/test_api_protobuf_generator.py @@ -202,7 +202,7 @@ def _decode_case(field_type: int, number: int, *, repeated: bool = False) -> str @pytest.mark.parametrize( ("needs_decode", "force", "member"), [ - (False, False, "StringRef value{nullptr, 0};"), + (False, False, "StringRef value{nullptr, 0}; // null until set, encode only"), (True, False, "StringRef value{};"), (False, True, "StringRef value{};"), ],