From 4c1caeb80968a5d55e631f7428416b6542d5b1bd Mon Sep 17 00:00:00 2001 From: Anthony Printup <92564080+anthonyprintup@users.noreply.github.com> Date: Fri, 1 May 2026 02:51:40 +0200 Subject: [PATCH 1/2] docs: document string_view kernel fallback Add a discouraged Windows kernel driver example for satisfying MSVC STL's internal _Xout_of_range helper so std::string_view::at() can compile without exceptions. Clarify that the shim should bugcheck on accidental checked access and that kernel or freestanding users should prefer Protocyte's Span API. --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index 5aec621..5fd4e6e 100644 --- a/README.md +++ b/README.md @@ -432,6 +432,50 @@ Common generated operations include: - `clone()` - field accessors, `has_*()`, `set_*()`, `mutable_*()`, and `ensure_*()` where applicable +### String Views + +Generated `string` field accessors return `::protocyte::Span` by +default. Protocyte does not return `std::string_view` by default because the +runtime is designed for freestanding and kernel-style builds that avoid +standard-library exception surfaces. `std::string_view` includes checked APIs +such as `at()` and some `substr()` overloads whose standard contract can throw +`std::out_of_range`; `::protocyte::Span` keeps the default string +view API in Protocyte's no-exceptions runtime surface. + +Hosted users who want standard-library interoperability can opt in: + +```cmake +target_compile_definitions(my_target PRIVATE PROTOCYTE_ENABLE_STD_STRING_VIEW=1) +``` + +When `PROTOCYTE_ENABLE_STD_STRING_VIEW` is defined, the runtime includes +`` and both `::protocyte::Span` / `Span` and +`::protocyte::String` are implicitly convertible to `std::string_view`. The +default accessor return type remains `Span`, so code that does not +enable the option keeps the smaller no-exception surface. + +In a Windows kernel driver, one technically possible MSVC/STL-specific escape +hatch is to provide the STL's internal out-of-range throw helper yourself so +`std::string_view::at()` can link even though exceptions are unavailable. This +should be treated as a last-resort compatibility shim, not as a recommended +Protocyte configuration: any accidental checked access would bugcheck the +system. + +```cpp +#include + +namespace std { +[[noreturn]] void __cdecl _Xout_of_range(char const*) { + KeBugCheckEx(MANUALLY_INITIATED_CRASH, 'svat', 0, 0, 0); + __assume(0); +} +} // namespace std +``` + +Prefer the default `::protocyte::Span` API in kernel and +freestanding builds. It avoids depending on implementation-private STL symbols +and keeps checked string access out of the generated-code runtime surface. + ### Parse Atomicity `merge_from(reader)` commits parsed data per wire field occurrence. If a field From 820634f8079bd1209c1513d0b083c8b2b1764e17 Mon Sep 17 00:00:00 2001 From: Anthony Printup <92564080+anthonyprintup@users.noreply.github.com> Date: Fri, 1 May 2026 02:54:37 +0200 Subject: [PATCH 2/2] feat: expose generated string views as char spans Return Span from generated string accessors while leaving bytes fields as byte spans. Add optional std::string_view conversions for char spans and runtime strings behind PROTOCYTE_ENABLE_STD_STRING_VIEW. Update smoke coverage, tests, and regenerated checked outputs for the string-view API. --- smoke/CMakeLists.txt | 8 +- smoke/generated/compat.protocyte.hpp | 10 +-- smoke/generated/example.protocyte.hpp | 20 ++--- smoke/generated/protocyte/runtime/runtime.hpp | 78 +++++++++++++++---- smoke/src/host_smoke.cpp | 7 +- src/protocyte/cpp.py | 6 +- src/protocyte/runtime/runtime.hpp | 78 +++++++++++++++---- tests/test_plugin.py | 25 +++++- 8 files changed, 179 insertions(+), 53 deletions(-) diff --git a/smoke/CMakeLists.txt b/smoke/CMakeLists.txt index eaf0903..ca1cc03 100644 --- a/smoke/CMakeLists.txt +++ b/smoke/CMakeLists.txt @@ -123,7 +123,12 @@ add_executable(protocyte_host_smoke "${PROTOCYTE_GENERATED_DIR}/cross_package.protocyte.cpp" ) target_include_directories(protocyte_host_smoke PRIVATE "${PROTOCYTE_GENERATED_DIR}") -target_compile_definitions(protocyte_host_smoke PRIVATE PROTOCYTE_ENABLE_HOSTED_ALLOCATOR=1) +target_compile_definitions( + protocyte_host_smoke + PRIVATE + PROTOCYTE_ENABLE_HOSTED_ALLOCATOR=1 + PROTOCYTE_ENABLE_STD_STRING_VIEW=1 +) target_compile_features(protocyte_host_smoke PRIVATE cxx_std_20) target_link_libraries(protocyte_host_smoke PRIVATE Catch2::Catch2WithMain) @@ -150,6 +155,7 @@ if(PROTOCYTE_SMOKE_BUILD_DRIVER) "${PROTOCYTE_GENERATED_DIR}/example.protocyte.cpp" ) target_include_directories(protocyte_kernel_smoke PRIVATE "${PROTOCYTE_GENERATED_DIR}") + target_compile_definitions(protocyte_kernel_smoke PRIVATE PROTOCYTE_ENABLE_STD_STRING_VIEW=1) target_compile_features(protocyte_kernel_smoke PRIVATE cxx_std_20) target_compile_options(protocyte_kernel_smoke PRIVATE /std:c++20 /permissive- /W4) diff --git a/smoke/generated/compat.protocyte.hpp b/smoke/generated/compat.protocyte.hpp index b5bdeba..39032c4 100644 --- a/smoke/generated/compat.protocyte.hpp +++ b/smoke/generated/compat.protocyte.hpp @@ -65,7 +65,7 @@ namespace protocyte_smoke::test::compat { } constexpr void clear_value() noexcept { value_ = {}; } - ::protocyte::Span label() const noexcept { return label_.view(); } + ::protocyte::Span label() const noexcept { return label_.view(); } typename Config::String &mutable_label() noexcept { return label_; } template::protocyte::Status set_label(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) @@ -652,7 +652,7 @@ namespace protocyte_smoke::test::compat { } constexpr void clear_f_double() noexcept { f_double_ = {}; } - ::protocyte::Span f_string() const noexcept { return f_string_.view(); } + ::protocyte::Span f_string() const noexcept { return f_string_.view(); } typename Config::String &mutable_f_string() noexcept { return f_string_; } template::protocyte::Status set_f_string(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) @@ -741,8 +741,8 @@ namespace protocyte_smoke::test::compat { constexpr bool has_oneof_string() const noexcept { return special_oneof_case_ == Special_oneofCase::oneof_string; } - ::protocyte::Span oneof_string() const noexcept { - return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::Span {}; + ::protocyte::Span oneof_string() const noexcept { + return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::Span {}; } template::protocyte::Status set_oneof_string(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) @@ -852,7 +852,7 @@ namespace protocyte_smoke::test::compat { has_opt_int32_ = false; } - ::protocyte::Span opt_string() const noexcept { return opt_string_.view(); } + ::protocyte::Span opt_string() const noexcept { return opt_string_.view(); } bool has_opt_string() const noexcept { return has_opt_string_; } typename Config::String &mutable_opt_string() noexcept { has_opt_string_ = true; diff --git a/smoke/generated/example.protocyte.hpp b/smoke/generated/example.protocyte.hpp index 0b7f429..21b3f4c 100644 --- a/smoke/generated/example.protocyte.hpp +++ b/smoke/generated/example.protocyte.hpp @@ -96,7 +96,7 @@ namespace test::ultimate { return out; } - ::protocyte::Span description() const noexcept { return description_.view(); } + ::protocyte::Span description() const noexcept { return description_.view(); } typename Config::String &mutable_description() noexcept { return description_; } template::protocyte::Status set_description(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) @@ -394,7 +394,7 @@ namespace test::ultimate { return out; } - ::protocyte::Span name() const noexcept { return name_.view(); } + ::protocyte::Span name() const noexcept { return name_.view(); } typename Config::String &mutable_name() noexcept { return name_; } template::protocyte::Status set_name(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) @@ -1118,7 +1118,7 @@ namespace test::ultimate { deep_oneof_case_ = Deep_oneofCase::none; } - ::protocyte::Span extreme() const noexcept { return extreme_.view(); } + ::protocyte::Span extreme() const noexcept { return extreme_.view(); } typename Config::String &mutable_extreme() noexcept { return extreme_; } template::protocyte::Status set_extreme(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) @@ -1168,8 +1168,8 @@ namespace test::ultimate { } constexpr bool has_text() const noexcept { return deep_oneof_case_ == Deep_oneofCase::text; } - ::protocyte::Span text() const noexcept { - return has_text() ? deep_oneof.text.view() : ::protocyte::Span {}; + ::protocyte::Span text() const noexcept { + return has_text() ? deep_oneof.text.view() : ::protocyte::Span {}; } template::protocyte::Status set_text(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) @@ -2297,7 +2297,7 @@ namespace test::ultimate { } constexpr void clear_f_bool() noexcept { f_bool_ = {}; } - ::protocyte::Span f_string() const noexcept { return f_string_.view(); } + ::protocyte::Span f_string() const noexcept { return f_string_.view(); } typename Config::String &mutable_f_string() noexcept { return f_string_; } template::protocyte::Status set_f_string(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) @@ -2399,8 +2399,8 @@ namespace test::ultimate { constexpr bool has_oneof_string() const noexcept { return special_oneof_case_ == Special_oneofCase::oneof_string; } - ::protocyte::Span oneof_string() const noexcept { - return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::Span {}; + ::protocyte::Span oneof_string() const noexcept { + return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::Span {}; } template::protocyte::Status set_oneof_string(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) @@ -2749,7 +2749,7 @@ namespace test::ultimate { has_opt_int32_ = false; } - ::protocyte::Span opt_string() const noexcept { return opt_string_.view(); } + ::protocyte::Span opt_string() const noexcept { return opt_string_.view(); } bool has_opt_string() const noexcept { return has_opt_string_; } typename Config::String &mutable_opt_string() noexcept { has_opt_string_ = true; @@ -6063,7 +6063,7 @@ namespace test::ultimate { return out; } - ::protocyte::Span tag() const noexcept { return tag_.view(); } + ::protocyte::Span tag() const noexcept { return tag_.view(); } typename Config::String &mutable_tag() noexcept { return tag_; } template::protocyte::Status set_tag(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) diff --git a/smoke/generated/protocyte/runtime/runtime.hpp b/smoke/generated/protocyte/runtime/runtime.hpp index a9f8483..6fb2585 100644 --- a/smoke/generated/protocyte/runtime/runtime.hpp +++ b/smoke/generated/protocyte/runtime/runtime.hpp @@ -11,6 +11,10 @@ #include #include +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#include +#endif + namespace protocyte { using i8 = ::std::int8_t; @@ -1265,6 +1269,13 @@ namespace protocyte { constexpr usize size() const noexcept { return size_; } constexpr usize size_bytes() const noexcept { return size_ * sizeof(T); } constexpr bool empty() const noexcept { return size_ == 0u; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + constexpr operator ::std::string_view() const noexcept + requires(::std::same_as<::std::remove_cv_t, char>) + { + return data_ == nullptr ? ::std::string_view {} : ::std::string_view {data_, size_}; + } +#endif template constexpr Span first() const noexcept requires(Extent == dynamic_extent || Count <= Extent) { @@ -1560,6 +1571,13 @@ namespace protocyte { return ::std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0; } + template + constexpr bool bytes_equal(const Span lhs, const Span rhs) noexcept + requires(!(::std::same_as<::std::remove_cv_t, u8> && ::std::same_as<::std::remove_cv_t, u8>) ) + { + return bytes_equal(as_bytes(lhs), as_bytes(rhs)); + } + constexpr bool bytes_zero(const Span view) noexcept { for (usize i {}; i < view.size(); ++i) { if (view.data()[i] != 0u) { @@ -1578,6 +1596,12 @@ namespace protocyte { return hash; } + template constexpr u64 fnv1a(const Span view) noexcept + requires(!::std::same_as<::std::remove_cv_t, u8>) + { + return fnv1a(as_bytes(view)); + } + template struct AlwaysFalse { static constexpr bool value = false; }; @@ -2815,11 +2839,11 @@ namespace protocyte { template struct String { using Context = typename Config::Context; - using value_type = const u8; - using iterator = typename Config::Bytes::const_iterator; - using const_iterator = typename Config::Bytes::const_iterator; - using reverse_iterator = typename Config::Bytes::const_reverse_iterator; - using const_reverse_iterator = typename Config::Bytes::const_reverse_iterator; + using value_type = const char; + using iterator = const char *; + using const_iterator = const char *; + using reverse_iterator = ReverseIterator; + using const_reverse_iterator = ReverseIterator; explicit String(Context *ctx = nullptr) noexcept: bytes_ {ctx} {} String(String &&other) noexcept: bytes_ {protocyte::move(other.bytes_)} {} @@ -2830,19 +2854,24 @@ namespace protocyte { String(const String &) = delete; String &operator=(const String &) = delete; - Span view() const noexcept { return bytes_.view(); } - const_iterator begin() const noexcept { return bytes_.begin(); } - const_iterator end() const noexcept { return bytes_.end(); } - const_iterator cbegin() const noexcept { return bytes_.cbegin(); } - const_iterator cend() const noexcept { return bytes_.cend(); } - const_reverse_iterator rbegin() const noexcept { return bytes_.rbegin(); } - const_reverse_iterator rend() const noexcept { return bytes_.rend(); } - const_reverse_iterator crbegin() const noexcept { return bytes_.crbegin(); } - const_reverse_iterator crend() const noexcept { return bytes_.crend(); } - const u8 *data() const noexcept { return bytes_.data(); } + Span view() const noexcept { return {data(), size()}; } + Span byte_view() const noexcept { return bytes_.view(); } + const_iterator begin() const noexcept { return data(); } + const_iterator end() const noexcept { return data() + size(); } + const_iterator cbegin() const noexcept { return begin(); } + const_iterator cend() const noexcept { return end(); } + const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator {end()}; } + const_reverse_iterator rend() const noexcept { return const_reverse_iterator {begin()}; } + const_reverse_iterator crbegin() const noexcept { return rbegin(); } + const_reverse_iterator crend() const noexcept { return rend(); } + const char *data() const noexcept { return reinterpret_cast(bytes_.data()); } Context *context() const noexcept { return bytes_.context(); } usize size() const noexcept { return bytes_.size(); } + usize length() const noexcept { return size(); } bool empty() const noexcept { return bytes_.empty(); } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + operator ::std::string_view() const noexcept { return view(); } +#endif void clear() noexcept { bytes_.clear(); } Span mutable_view_for_overwrite() noexcept { return bytes_.mutable_view(); } @@ -2853,6 +2882,14 @@ namespace protocyte { return bytes_.resize_for_overwrite(count); } + Status assign(const Span view) noexcept { + const auto bytes = byte_span_of(view); + if (!bytes) { + return bytes.status(); + } + return assign(*bytes); + } + Status assign(const Span view) noexcept { if (const auto st = check_size_limit(view.size()); !st) { return st; @@ -4229,6 +4266,17 @@ namespace protocyte { return write_bytes_field(writer, field_number, view); } + template + Status write_string_field(Writer &writer, const u32 field_number, const Span view) noexcept + requires(TextChar) + { + const auto bytes = byte_span_of(view); + if (!bytes) { + return bytes.status(); + } + return write_string_field(writer, field_number, *bytes); + } + inline Result add_size(const usize total, const usize value) noexcept { return checked_add(total, value); } #ifdef PROTOCYTE_ENABLE_HOSTED_ALLOCATOR diff --git a/smoke/src/host_smoke.cpp b/smoke/src/host_smoke.cpp index b97fc0e..31e92fb 100644 --- a/smoke/src/host_smoke.cpp +++ b/smoke/src/host_smoke.cpp @@ -208,7 +208,8 @@ namespace { }; } - bool view_equal(protocyte::Span lhs, protocyte::Span rhs) noexcept { + template + bool view_equal(protocyte::Span lhs, protocyte::Span rhs) noexcept { return protocyte::bytes_equal(lhs, rhs); } @@ -2711,6 +2712,10 @@ TEST_CASE("byte setters accept contiguous byte containers", "[smoke][runtime][by const auto string_view = protocyte::byte_span_of(string_payload); REQUIRE(string_view); CHECK(view_equal(message.f_string(), *string_view)); + const std::string_view converted_span = message.f_string(); + CHECK(converted_span == std::string_view {"hello"}); + const std::string_view converted_string = message.mutable_f_string(); + CHECK(converted_string == converted_span); require_success(message.set_f_string("hello from literal")); CHECK(message.f_string().size() == std::string_view {"hello from literal"}.size()); diff --git a/src/protocyte/cpp.py b/src/protocyte/cpp.py index 1aa1a47..0f5e236 100644 --- a/src/protocyte/cpp.py +++ b/src/protocyte/cpp.py @@ -813,7 +813,8 @@ def emit_setter_body() -> None: return if item.kind in {"string", "bytes"}: typ = _field_type(item, options) - w.line(f"::protocyte::Span {item.cpp_name}() const noexcept {{ return {_member(item)}.view(); }}") + view_type = "::protocyte::Span" if item.kind == "string" else "::protocyte::Span" + w.line(f"{view_type} {item.cpp_name}() const noexcept {{ return {_member(item)}.view(); }}") if item.proto3_optional: w.line(f"bool has_{item.cpp_name}() const noexcept {{ return has_{item.cpp_name}_; }}") w.line(f"{typ}& mutable_{item.cpp_name}() noexcept {{") @@ -878,8 +879,9 @@ def _emit_oneof_accessors(w: CppWriter, item: FieldModel, options: GeneratorOpti f"constexpr bool has_{item.cpp_name}() const noexcept {{ return {case_member} == {case_type}::{item.cpp_name}; }}" ) if item.kind in {"string", "bytes"}: + view_type = "::protocyte::Span" if item.kind == "string" else "::protocyte::Span" w.line( - f"::protocyte::Span {item.cpp_name}() const noexcept {{ return has_{item.cpp_name}() ? {_member(item)}.view() : ::protocyte::Span{{}}; }}" + f"{view_type} {item.cpp_name}() const noexcept {{ return has_{item.cpp_name}() ? {_member(item)}.view() : {view_type}{{}}; }}" ) def emit_setter_body() -> None: if item.kind == "bytes" and item.array_enabled: diff --git a/src/protocyte/runtime/runtime.hpp b/src/protocyte/runtime/runtime.hpp index a9f8483..6fb2585 100644 --- a/src/protocyte/runtime/runtime.hpp +++ b/src/protocyte/runtime/runtime.hpp @@ -11,6 +11,10 @@ #include #include +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#include +#endif + namespace protocyte { using i8 = ::std::int8_t; @@ -1265,6 +1269,13 @@ namespace protocyte { constexpr usize size() const noexcept { return size_; } constexpr usize size_bytes() const noexcept { return size_ * sizeof(T); } constexpr bool empty() const noexcept { return size_ == 0u; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + constexpr operator ::std::string_view() const noexcept + requires(::std::same_as<::std::remove_cv_t, char>) + { + return data_ == nullptr ? ::std::string_view {} : ::std::string_view {data_, size_}; + } +#endif template constexpr Span first() const noexcept requires(Extent == dynamic_extent || Count <= Extent) { @@ -1560,6 +1571,13 @@ namespace protocyte { return ::std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0; } + template + constexpr bool bytes_equal(const Span lhs, const Span rhs) noexcept + requires(!(::std::same_as<::std::remove_cv_t, u8> && ::std::same_as<::std::remove_cv_t, u8>) ) + { + return bytes_equal(as_bytes(lhs), as_bytes(rhs)); + } + constexpr bool bytes_zero(const Span view) noexcept { for (usize i {}; i < view.size(); ++i) { if (view.data()[i] != 0u) { @@ -1578,6 +1596,12 @@ namespace protocyte { return hash; } + template constexpr u64 fnv1a(const Span view) noexcept + requires(!::std::same_as<::std::remove_cv_t, u8>) + { + return fnv1a(as_bytes(view)); + } + template struct AlwaysFalse { static constexpr bool value = false; }; @@ -2815,11 +2839,11 @@ namespace protocyte { template struct String { using Context = typename Config::Context; - using value_type = const u8; - using iterator = typename Config::Bytes::const_iterator; - using const_iterator = typename Config::Bytes::const_iterator; - using reverse_iterator = typename Config::Bytes::const_reverse_iterator; - using const_reverse_iterator = typename Config::Bytes::const_reverse_iterator; + using value_type = const char; + using iterator = const char *; + using const_iterator = const char *; + using reverse_iterator = ReverseIterator; + using const_reverse_iterator = ReverseIterator; explicit String(Context *ctx = nullptr) noexcept: bytes_ {ctx} {} String(String &&other) noexcept: bytes_ {protocyte::move(other.bytes_)} {} @@ -2830,19 +2854,24 @@ namespace protocyte { String(const String &) = delete; String &operator=(const String &) = delete; - Span view() const noexcept { return bytes_.view(); } - const_iterator begin() const noexcept { return bytes_.begin(); } - const_iterator end() const noexcept { return bytes_.end(); } - const_iterator cbegin() const noexcept { return bytes_.cbegin(); } - const_iterator cend() const noexcept { return bytes_.cend(); } - const_reverse_iterator rbegin() const noexcept { return bytes_.rbegin(); } - const_reverse_iterator rend() const noexcept { return bytes_.rend(); } - const_reverse_iterator crbegin() const noexcept { return bytes_.crbegin(); } - const_reverse_iterator crend() const noexcept { return bytes_.crend(); } - const u8 *data() const noexcept { return bytes_.data(); } + Span view() const noexcept { return {data(), size()}; } + Span byte_view() const noexcept { return bytes_.view(); } + const_iterator begin() const noexcept { return data(); } + const_iterator end() const noexcept { return data() + size(); } + const_iterator cbegin() const noexcept { return begin(); } + const_iterator cend() const noexcept { return end(); } + const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator {end()}; } + const_reverse_iterator rend() const noexcept { return const_reverse_iterator {begin()}; } + const_reverse_iterator crbegin() const noexcept { return rbegin(); } + const_reverse_iterator crend() const noexcept { return rend(); } + const char *data() const noexcept { return reinterpret_cast(bytes_.data()); } Context *context() const noexcept { return bytes_.context(); } usize size() const noexcept { return bytes_.size(); } + usize length() const noexcept { return size(); } bool empty() const noexcept { return bytes_.empty(); } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + operator ::std::string_view() const noexcept { return view(); } +#endif void clear() noexcept { bytes_.clear(); } Span mutable_view_for_overwrite() noexcept { return bytes_.mutable_view(); } @@ -2853,6 +2882,14 @@ namespace protocyte { return bytes_.resize_for_overwrite(count); } + Status assign(const Span view) noexcept { + const auto bytes = byte_span_of(view); + if (!bytes) { + return bytes.status(); + } + return assign(*bytes); + } + Status assign(const Span view) noexcept { if (const auto st = check_size_limit(view.size()); !st) { return st; @@ -4229,6 +4266,17 @@ namespace protocyte { return write_bytes_field(writer, field_number, view); } + template + Status write_string_field(Writer &writer, const u32 field_number, const Span view) noexcept + requires(TextChar) + { + const auto bytes = byte_span_of(view); + if (!bytes) { + return bytes.status(); + } + return write_string_field(writer, field_number, *bytes); + } + inline Result add_size(const usize total, const usize value) noexcept { return checked_add(total, value); } #ifdef PROTOCYTE_ENABLE_HOSTED_ALLOCATOR diff --git a/tests/test_plugin.py b/tests/test_plugin.py index d211713..f193a29 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -159,6 +159,12 @@ def test_runtime_byte_containers_use_bulk_copy_helpers() -> None: bytes_body = runtime_header.split("template struct Bytes {", maxsplit=1)[1].split( "template struct String {", maxsplit=1 )[0] + span_body = runtime_header.split("template struct Span {", maxsplit=1)[1].split( + "template Span(T *, usize) -> Span;", maxsplit=1 + )[0] + string_body = runtime_header.split("template struct String {", maxsplit=1)[1].split( + "template struct Box {", maxsplit=1 + )[0] slice_reader_body = runtime_header.split("struct SliceReader {", maxsplit=1)[1].split( "struct ReaderRef {", maxsplit=1 )[0] @@ -167,6 +173,7 @@ def test_runtime_byte_containers_use_bulk_copy_helpers() -> None: )[0] assert "#include " in runtime_header + assert "#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW\n#include \n#endif" in runtime_header assert "inline void copy_bytes(u8 *dst, const u8 *src, const usize count) noexcept" in runtime_header assert "if (!count || dst == src)" in runtime_header assert "::std::memmove(dst, src, count);" in runtime_header @@ -189,6 +196,16 @@ def test_runtime_byte_containers_use_bulk_copy_helpers() -> None: assert "Status resize_for_overwrite(const usize count) noexcept" in fixed_byte_array_body assert "return bytes_.resize_for_overwrite(count);" in bytes_body assert "copy_bytes(temp.data(), view.data(), view.size());" in bytes_body + assert "constexpr operator ::std::string_view() const noexcept" in span_body + assert "requires(::std::same_as<::std::remove_cv_t, char>)" in span_body + assert "using value_type = const char;" in string_body + assert "Span view() const noexcept" in string_body + assert "Span byte_view() const noexcept" in string_body + assert "const char *data() const noexcept" in string_body + assert "usize length() const noexcept { return size(); }" in string_body + assert "operator ::std::string_view() const noexcept { return view(); }" in string_body + assert "Status assign(const Span view) noexcept" in string_body + assert "Status assign(const Span view) noexcept" in string_body assert "copy_bytes(out, data_ + pos_, count);" in slice_reader_body assert "copy_bytes(data_ + pos_, data, count);" in slice_writer_body assert "for (usize i {}; i < count; ++i)" not in slice_reader_body @@ -354,10 +371,8 @@ def test_generates_proto3_files_and_runtime() -> None: ] assert "using reverse_iterator = ReverseIterator;" in files["protocyte/runtime/runtime.hpp"] assert "offset_ptr(" not in files["protocyte/runtime/runtime.hpp"] - assert "using iterator = typename Config::Bytes::const_iterator;" in files["protocyte/runtime/runtime.hpp"] - assert "using reverse_iterator = typename Config::Bytes::const_reverse_iterator;" in files[ - "protocyte/runtime/runtime.hpp" - ] + assert "using iterator = const char *;" in files["protocyte/runtime/runtime.hpp"] + assert "using reverse_iterator = ReverseIterator;" in files["protocyte/runtime/runtime.hpp"] assert "using Bucket = Optional;" in files["protocyte/runtime/runtime.hpp"] assert "struct EntryProxy {" in files["protocyte/runtime/runtime.hpp"] assert "struct ConstEntryProxy {" in files["protocyte/runtime/runtime.hpp"] @@ -661,6 +676,7 @@ def test_generated_header_contains_expected_field_api() -> None: assert "namespace demo {" in header assert "bool has_opt_name() const noexcept" in header + assert "::protocyte::Span opt_name() const noexcept" in header assert "struct Sample {" in header assert "typename Config::template Map items_;" in header assert "typename Config::template Box<::demo::Sample> self_;" in header @@ -1401,6 +1417,7 @@ def test_generated_header_emits_tagged_union_oneofs() -> None: assert "void clear_choice() noexcept {" in header assert "destroy_at_(&choice.text);" in header assert "destroy_at_(&choice.inner);" in header + assert "::protocyte::Span text() const noexcept" in header assert "union ChoiceStorage {" in header assert "ChoiceStorage() noexcept {}" in header assert "~ChoiceStorage() noexcept {}" in header