From fb529ec8139b61c35d66d039b3c92512ca0cbbf3 Mon Sep 17 00:00:00 2001 From: Anthony Printup <92564080+anthonyprintup@users.noreply.github.com> Date: Fri, 1 May 2026 20:54:06 +0200 Subject: [PATCH 1/5] fix: expose hosted string accessors as string_view Emit immutable string field accessors as std::string_view when PROTOCYTE_ENABLE_STD_STRING_VIEW is enabled while preserving Span for default builds. Simplify Span conversion to construct std::string_view directly from data and size, treating null non-empty spans as a contract violation. Regenerate checked smoke headers and add generator/smoke coverage for the hosted accessor surface. --- README.md | 8 +-- smoke/generated/compat.protocyte.hpp | 22 ++++++++ smoke/generated/example.protocyte.hpp | 34 ++++++++++++ smoke/generated/protocyte/runtime/runtime.hpp | 2 +- smoke/src/host_smoke.cpp | 17 ++++++ src/protocyte/cpp.py | 54 +++++++++++++++---- src/protocyte/runtime/runtime.hpp | 2 +- tests/test_plugin.py | 20 ++++++- 8 files changed, 143 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 5fd4e6e..7bf80d9 100644 --- a/README.md +++ b/README.md @@ -450,9 +450,11 @@ 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. +`::protocyte::String` are implicitly convertible to `std::string_view`. +Generated immutable `string` field accessors also return `std::string_view` +under this opt-in, so hosted code can pass string fields directly to +standard-library APIs such as `std::format`. Code that does not enable the +option keeps the smaller no-exception `Span` accessor 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 diff --git a/smoke/generated/compat.protocyte.hpp b/smoke/generated/compat.protocyte.hpp index 39032c4..bbd1202 100644 --- a/smoke/generated/compat.protocyte.hpp +++ b/smoke/generated/compat.protocyte.hpp @@ -5,6 +5,10 @@ #include +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#include +#endif + namespace protocyte_smoke::test::compat { enum struct EncodingMatrix_Mode : ::protocyte::i32 { @@ -65,7 +69,11 @@ namespace protocyte_smoke::test::compat { } constexpr void clear_value() noexcept { value_ = {}; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + ::std::string_view label() const noexcept { return label_.view(); } +#else ::protocyte::Span label() const noexcept { return label_.view(); } +#endif typename Config::String &mutable_label() noexcept { return label_; } template::protocyte::Status set_label(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) @@ -652,7 +660,11 @@ namespace protocyte_smoke::test::compat { } constexpr void clear_f_double() noexcept { f_double_ = {}; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + ::std::string_view f_string() const noexcept { return f_string_.view(); } +#else ::protocyte::Span f_string() const noexcept { return f_string_.view(); } +#endif 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,9 +753,15 @@ namespace protocyte_smoke::test::compat { constexpr bool has_oneof_string() const noexcept { return special_oneof_case_ == Special_oneofCase::oneof_string; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + ::std::string_view oneof_string() const noexcept { + return has_oneof_string() ? special_oneof.oneof_string.view() : ::std::string_view {}; + } +#else ::protocyte::Span oneof_string() const noexcept { return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::Span {}; } +#endif template::protocyte::Status set_oneof_string(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) { @@ -852,7 +870,11 @@ namespace protocyte_smoke::test::compat { has_opt_int32_ = false; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + ::std::string_view opt_string() const noexcept { return opt_string_.view(); } +#else ::protocyte::Span opt_string() const noexcept { return opt_string_.view(); } +#endif 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 21b3f4c..043279a 100644 --- a/smoke/generated/example.protocyte.hpp +++ b/smoke/generated/example.protocyte.hpp @@ -96,7 +96,11 @@ namespace test::ultimate { return out; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + ::std::string_view description() const noexcept { return description_.view(); } +#else ::protocyte::Span description() const noexcept { return description_.view(); } +#endif typename Config::String &mutable_description() noexcept { return description_; } template::protocyte::Status set_description(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) @@ -394,7 +398,11 @@ namespace test::ultimate { return out; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + ::std::string_view name() const noexcept { return name_.view(); } +#else ::protocyte::Span name() const noexcept { return name_.view(); } +#endif typename Config::String &mutable_name() noexcept { return name_; } template::protocyte::Status set_name(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) @@ -1118,7 +1126,11 @@ namespace test::ultimate { deep_oneof_case_ = Deep_oneofCase::none; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + ::std::string_view extreme() const noexcept { return extreme_.view(); } +#else ::protocyte::Span extreme() const noexcept { return extreme_.view(); } +#endif typename Config::String &mutable_extreme() noexcept { return extreme_; } template::protocyte::Status set_extreme(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) @@ -1168,9 +1180,13 @@ namespace test::ultimate { } constexpr bool has_text() const noexcept { return deep_oneof_case_ == Deep_oneofCase::text; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + ::std::string_view text() const noexcept { return has_text() ? deep_oneof.text.view() : ::std::string_view {}; } +#else ::protocyte::Span text() const noexcept { return has_text() ? deep_oneof.text.view() : ::protocyte::Span {}; } +#endif template::protocyte::Status set_text(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) { @@ -2297,7 +2313,11 @@ namespace test::ultimate { } constexpr void clear_f_bool() noexcept { f_bool_ = {}; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + ::std::string_view f_string() const noexcept { return f_string_.view(); } +#else ::protocyte::Span f_string() const noexcept { return f_string_.view(); } +#endif 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,9 +2419,15 @@ namespace test::ultimate { constexpr bool has_oneof_string() const noexcept { return special_oneof_case_ == Special_oneofCase::oneof_string; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + ::std::string_view oneof_string() const noexcept { + return has_oneof_string() ? special_oneof.oneof_string.view() : ::std::string_view {}; + } +#else ::protocyte::Span oneof_string() const noexcept { return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::Span {}; } +#endif template::protocyte::Status set_oneof_string(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) { @@ -2749,7 +2775,11 @@ namespace test::ultimate { has_opt_int32_ = false; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + ::std::string_view opt_string() const noexcept { return opt_string_.view(); } +#else ::protocyte::Span opt_string() const noexcept { return opt_string_.view(); } +#endif bool has_opt_string() const noexcept { return has_opt_string_; } typename Config::String &mutable_opt_string() noexcept { has_opt_string_ = true; @@ -6063,7 +6093,11 @@ namespace test::ultimate { return out; } +#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW + ::std::string_view tag() const noexcept { return tag_.view(); } +#else ::protocyte::Span tag() const noexcept { return tag_.view(); } +#endif 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 6fb2585..314c868 100644 --- a/smoke/generated/protocyte/runtime/runtime.hpp +++ b/smoke/generated/protocyte/runtime/runtime.hpp @@ -1273,7 +1273,7 @@ namespace protocyte { 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_}; + return ::std::string_view {data_, size_}; } #endif template constexpr Span first() const noexcept diff --git a/smoke/src/host_smoke.cpp b/smoke/src/host_smoke.cpp index bf8eef9..7fc0746 100644 --- a/smoke/src/host_smoke.cpp +++ b/smoke/src/host_smoke.cpp @@ -2,6 +2,9 @@ #include #include #include +#if __has_include() +#include +#endif #include #include #include @@ -214,6 +217,16 @@ namespace { return protocyte::bytes_equal(lhs, rhs); } + template + bool view_equal(std::string_view lhs, protocyte::Span rhs) noexcept { + return view_equal(protocyte::Span {lhs.data(), lhs.size()}, rhs); + } + + template + bool view_equal(protocyte::Span lhs, std::string_view rhs) noexcept { + return view_equal(lhs, protocyte::Span {rhs.data(), rhs.size()}); + } + template protocyte::Span view_of(const uint8_t (&data)[N]) noexcept { return protocyte::Span {data, N}; } @@ -2603,8 +2616,12 @@ 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)); + static_assert(std::is_same_v); const std::string_view converted_span = message.f_string(); CHECK(converted_span == std::string_view {"hello"}); +#if defined(__cpp_lib_format) + CHECK(std::format("{}", message.f_string()) == "hello"); +#endif const std::string_view converted_string = message.mutable_f_string(); CHECK(converted_string == converted_span); diff --git a/src/protocyte/cpp.py b/src/protocyte/cpp.py index 0f5e236..2466119 100644 --- a/src/protocyte/cpp.py +++ b/src/protocyte/cpp.py @@ -211,6 +211,8 @@ def generate_header(file_model: FileModel, options: GeneratorOptions) -> str: extra_includes: list[str] = [] if _file_uses_string_view(file_model): extra_includes.append("#include ") + elif _file_uses_std_string_view_accessors(file_model): + extra_includes.extend(["#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW", "#include ", "#endif"]) for dependency in sorted(file_model.dependencies): extra_includes.append(f'#include "{_include_path(dependency, options)}"') if extra_includes: @@ -813,11 +815,13 @@ def emit_setter_body() -> None: return if item.kind in {"string", "bytes"}: typ = _field_type(item, options) - 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 {{") + if item.kind == "string": + _emit_string_view_accessor(w, item.cpp_name, f"{_member(item)}.view()") + else: + w.line(f"::protocyte::Span {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 {{") w.push() if item.proto3_optional: w.line(f"has_{item.cpp_name}_ = true;") @@ -879,10 +883,18 @@ 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"{view_type} {item.cpp_name}() const noexcept {{ return has_{item.cpp_name}() ? {_member(item)}.view() : {view_type}{{}}; }}" - ) + if item.kind == "string": + _emit_string_view_accessor( + w, + item.cpp_name, + f"has_{item.cpp_name}() ? {_member(item)}.view() : ::std::string_view{{}}", + f"has_{item.cpp_name}() ? {_member(item)}.view() : ::protocyte::Span{{}}", + ) + else: + view_type = "::protocyte::Span" + w.line( + 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: w.line( @@ -1961,6 +1973,21 @@ def _runtime_scalar_type(cpp_type: str) -> str: return _RUNTIME_SCALAR_TYPES.get(cpp_type, cpp_type) +def _emit_string_view_accessor( + w: CppWriter, + name: str, + string_view_expr: str, + span_expr: str | None = None, +) -> None: + if span_expr is None: + span_expr = string_view_expr + w.line("#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW") + w.line(f"::std::string_view {name}() const noexcept {{ return {string_view_expr}; }}") + w.line("#else") + w.line(f"::protocyte::Span {name}() const noexcept {{ return {span_expr}; }}") + w.line("#endif") + + def _file_uses_string_view(file_model: FileModel) -> bool: if any(constant.kind == CONSTANT_KIND_STRING for constant in file_model.constants): return True @@ -1970,6 +1997,15 @@ def _file_uses_string_view(file_model: FileModel) -> bool: return False +def _file_uses_std_string_view_accessors(file_model: FileModel) -> bool: + for message in _walk_messages(file_model.messages): + if message.is_map_entry: + continue + if any(item.kind == "string" and not item.repeated for item in message.fields): + return True + return False + + def _ordered_messages(file_model: FileModel) -> list[MessageModel]: all_messages = [item for item in _walk_messages(file_model.messages) if not item.is_map_entry] by_name = {item.full_name: item for item in all_messages} diff --git a/src/protocyte/runtime/runtime.hpp b/src/protocyte/runtime/runtime.hpp index 6fb2585..314c868 100644 --- a/src/protocyte/runtime/runtime.hpp +++ b/src/protocyte/runtime/runtime.hpp @@ -1273,7 +1273,7 @@ namespace protocyte { 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_}; + return ::std::string_view {data_, size_}; } #endif template constexpr Span first() const noexcept diff --git a/tests/test_plugin.py b/tests/test_plugin.py index f193a29..53ccadb 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -198,6 +198,8 @@ def test_runtime_byte_containers_use_bulk_copy_helpers() -> None: 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 "return ::std::string_view {data_, size_};" in span_body + assert "data_ == nullptr ? ::std::string_view {}" not 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 @@ -676,7 +678,14 @@ 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 "#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW\n#include \n#endif" in header + assert ( + " #ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW\n" + " ::std::string_view opt_name() const noexcept { return opt_name_.view(); }\n" + " #else\n" + " ::protocyte::Span opt_name() const noexcept { return opt_name_.view(); }\n" + " #endif" + ) 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 @@ -1417,7 +1426,14 @@ 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 "#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW\n#include \n#endif" in header + assert ( + " #ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW\n" + " ::std::string_view text() const noexcept { return has_text() ? choice.text.view() : ::std::string_view{}; }\n" + " #else\n" + " ::protocyte::Span text() const noexcept { return has_text() ? choice.text.view() : ::protocyte::Span{}; }\n" + " #endif" + ) in header assert "union ChoiceStorage {" in header assert "ChoiceStorage() noexcept {}" in header assert "~ChoiceStorage() noexcept {}" in header From 0da0b1195cdf51918b09358058158b0d64ccc15a Mon Sep 17 00:00:00 2001 From: Anthony Printup <92564080+anthonyprintup@users.noreply.github.com> Date: Fri, 1 May 2026 21:18:52 +0200 Subject: [PATCH 2/5] fix: provide debug kernel string_view crt shims Add smoke-driver-only fallback symbols for the MSVC debug STL CRT assertion hooks pulled in by std::string_view when PROTOCYTE_ENABLE_STD_STRING_VIEW is enabled. Route unexpected shim calls through DbgPrintEx and KeBugCheckEx, and cover the guard with a source-level regression test. --- smoke/src/kernel_driver_smoke.cpp | 28 ++++++++++++++++++++++++++++ tests/test_plugin.py | 10 ++++++++++ 2 files changed, 38 insertions(+) diff --git a/smoke/src/kernel_driver_smoke.cpp b/smoke/src/kernel_driver_smoke.cpp index 5e422de..6f44f0a 100644 --- a/smoke/src/kernel_driver_smoke.cpp +++ b/smoke/src/kernel_driver_smoke.cpp @@ -24,6 +24,34 @@ void __cdecl operator delete[](void *ptr) noexcept { ::operator delete(ptr); } void __cdecl operator delete[](void *ptr, size_t) noexcept { ::operator delete(ptr); } #endif +#if defined(PROTOCYTE_ENABLE_STD_STRING_VIEW) && defined(_DEBUG) +// MSVC's debug STL imports these CRT assertion hooks through __imp_* data symbols. +__declspec(noreturn) void protocyte_debug_crt_shim_bugcheck(const char *symbol_name) { + DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, + "Protocyte kernel smoke: MSVC STL debug CRT fallback called for %s.\n", symbol_name); + KeBugCheckEx(MANUALLY_INITIATED_CRASH, 0x50535643u, 0u, 0u, 0u); + for (;;) {} +} + +extern "C" __declspec(noreturn) void __cdecl protocyte_invoke_watson_shim(const wchar_t *, const wchar_t *, + const wchar_t *, unsigned int, uintptr_t) { + protocyte_debug_crt_shim_bugcheck("_invoke_watson"); +} + +extern "C" int __cdecl protocyte_crt_dbg_report_shim(int, const char *, int, const char *, const char *, ...) { + protocyte_debug_crt_shim_bugcheck("_CrtDbgReport"); +} + +using protocyte_invoke_watson_fn = void(__cdecl *)(const wchar_t *, const wchar_t *, const wchar_t *, unsigned int, + uintptr_t); +using protocyte_crt_dbg_report_fn = int(__cdecl *)(int, const char *, int, const char *, const char *, ...); + +extern "C" { + protocyte_invoke_watson_fn __imp__invoke_watson = &protocyte_invoke_watson_shim; + protocyte_crt_dbg_report_fn __imp__CrtDbgReport = &protocyte_crt_dbg_report_shim; +} +#endif + namespace { constexpr ULONG protocyte_pool_tag = 'TyCP'; diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 53ccadb..e1a783e 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -53,6 +53,16 @@ def test_runtime_rejects_unmatched_end_group_in_skip_field() -> None: assert "case WireType::EGROUP: return {};" not in runtime_header +def test_kernel_smoke_provides_debug_string_view_crt_shims() -> None: + source = (Path(__file__).resolve().parents[1] / "smoke" / "src" / "kernel_driver_smoke.cpp").read_text() + + assert "#if defined(PROTOCYTE_ENABLE_STD_STRING_VIEW) && defined(_DEBUG)" in source + assert "__imp__invoke_watson" in source + assert "__imp__CrtDbgReport" in source + assert "DbgPrintEx" in source + assert "KeBugCheckEx" in source + + def test_runtime_container_growth_checks_capacity_limits() -> None: runtime_header = runtime_files()["protocyte/runtime/runtime.hpp"] From f8c4539e3c36edafd2a3315e86fb975430559a5d Mon Sep 17 00:00:00 2001 From: Anthony Printup <92564080+anthonyprintup@users.noreply.github.com> Date: Fri, 1 May 2026 21:43:32 +0200 Subject: [PATCH 3/5] fix: avoid redundant generated string_view includes Rely on the runtime header for PROTOCYTE_ENABLE_STD_STRING_VIEW accessor support instead of emitting a guarded string_view include from generated message headers. Keep string-constant headers self-contained when the opt-in macro is off by emitting string_view only under the inverse guard, and update checked smoke outputs. --- smoke/generated/compat.protocyte.hpp | 4 ---- smoke/generated/cross_package.protocyte.hpp | 2 ++ smoke/generated/example.protocyte.hpp | 2 ++ src/protocyte/cpp.py | 13 +------------ tests/test_plugin.py | 20 ++++++++++++++------ 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/smoke/generated/compat.protocyte.hpp b/smoke/generated/compat.protocyte.hpp index bbd1202..ad29399 100644 --- a/smoke/generated/compat.protocyte.hpp +++ b/smoke/generated/compat.protocyte.hpp @@ -5,10 +5,6 @@ #include -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW -#include -#endif - namespace protocyte_smoke::test::compat { enum struct EncodingMatrix_Mode : ::protocyte::i32 { diff --git a/smoke/generated/cross_package.protocyte.hpp b/smoke/generated/cross_package.protocyte.hpp index a4d9029..ed1cc9b 100644 --- a/smoke/generated/cross_package.protocyte.hpp +++ b/smoke/generated/cross_package.protocyte.hpp @@ -5,7 +5,9 @@ #include +#ifndef PROTOCYTE_ENABLE_STD_STRING_VIEW #include +#endif namespace test::crosspkg { diff --git a/smoke/generated/example.protocyte.hpp b/smoke/generated/example.protocyte.hpp index 043279a..1e48a15 100644 --- a/smoke/generated/example.protocyte.hpp +++ b/smoke/generated/example.protocyte.hpp @@ -5,7 +5,9 @@ #include +#ifndef PROTOCYTE_ENABLE_STD_STRING_VIEW #include +#endif namespace test::ultimate { diff --git a/src/protocyte/cpp.py b/src/protocyte/cpp.py index 2466119..14a5047 100644 --- a/src/protocyte/cpp.py +++ b/src/protocyte/cpp.py @@ -210,9 +210,7 @@ def generate_header(file_model: FileModel, options: GeneratorOptions) -> str: w.line(f"#include <{options.runtime_prefix}/runtime.hpp>") extra_includes: list[str] = [] if _file_uses_string_view(file_model): - extra_includes.append("#include ") - elif _file_uses_std_string_view_accessors(file_model): - extra_includes.extend(["#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW", "#include ", "#endif"]) + extra_includes.extend(["#ifndef PROTOCYTE_ENABLE_STD_STRING_VIEW", "#include ", "#endif"]) for dependency in sorted(file_model.dependencies): extra_includes.append(f'#include "{_include_path(dependency, options)}"') if extra_includes: @@ -1997,15 +1995,6 @@ def _file_uses_string_view(file_model: FileModel) -> bool: return False -def _file_uses_std_string_view_accessors(file_model: FileModel) -> bool: - for message in _walk_messages(file_model.messages): - if message.is_map_entry: - continue - if any(item.kind == "string" and not item.repeated for item in message.fields): - return True - return False - - def _ordered_messages(file_model: FileModel) -> list[MessageModel]: all_messages = [item for item in _walk_messages(file_model.messages) if not item.is_map_entry] by_name = {item.full_name: item for item in all_messages} diff --git a/tests/test_plugin.py b/tests/test_plugin.py index e1a783e..66d5e2e 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -688,7 +688,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 "#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW\n#include \n#endif" in header + assert "#include " not in header assert ( " #ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW\n" " ::std::string_view opt_name() const noexcept { return opt_name_.view(); }\n" @@ -1056,8 +1056,12 @@ def test_generated_header_emits_constants_and_array_storage() -> None: header = files["arrays.protocyte.hpp"] runtime_header = files["protocyte/runtime/runtime.hpp"] - assert '#include \n\n#include ' in header - assert "#include " in header + assert ( + "#include \n\n" + "#ifndef PROTOCYTE_ENABLE_STD_STRING_VIEW\n" + "#include \n" + "#endif" + ) in header assert "inline constexpr ::protocyte::u32 FILE_CAP {16u};" in header assert 'inline constexpr ::std::string_view FILE_LABEL {"ell", 3u};' in header assert "inline constexpr bool FILE_READY {true};" in header @@ -1391,8 +1395,12 @@ def test_generated_header_emits_utf8_string_constants() -> None: assert not response.error header = next(file.content for file in response.file if file.name == "unicode.protocyte.hpp") - assert '#include \n\n#include ' in header - assert '#include ' in header + assert ( + "#include \n\n" + "#ifndef PROTOCYTE_ENABLE_STD_STRING_VIEW\n" + "#include \n" + "#endif" + ) in header assert 'static constexpr ::std::string_view NAME {"\\xc4"' in header assert '"\\x80"' in header assert '"\\xc3"' in header @@ -1436,7 +1444,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 "#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW\n#include \n#endif" in header + assert "#include " not in header assert ( " #ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW\n" " ::std::string_view text() const noexcept { return has_text() ? choice.text.view() : ::std::string_view{}; }\n" From b0852b57232eca19e62879834bb98f773ca3bed6 Mon Sep 17 00:00:00 2001 From: Anthony Printup <92564080+anthonyprintup@users.noreply.github.com> Date: Fri, 1 May 2026 21:56:05 +0200 Subject: [PATCH 4/5] fix: honor disabled string view macro value Switch PROTOCYTE_ENABLE_STD_STRING_VIEW guards from definedness checks to value checks so -DPROTOCYTE_ENABLE_STD_STRING_VIEW=0 uses the non-string_view accessor path. Update inverse generated string constant includes, kernel smoke guards, documentation, tests, and regenerated smoke outputs. --- README.md | 10 +++++----- smoke/generated/compat.protocyte.hpp | 8 ++++---- smoke/generated/cross_package.protocyte.hpp | 2 +- smoke/generated/example.protocyte.hpp | 18 +++++++++--------- smoke/generated/protocyte/runtime/runtime.hpp | 6 +++--- smoke/src/kernel_driver_smoke.cpp | 2 +- src/protocyte/cpp.py | 4 ++-- src/protocyte/runtime/runtime.hpp | 6 +++--- tests/test_plugin.py | 13 +++++++------ 9 files changed, 35 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 7bf80d9..e070100 100644 --- a/README.md +++ b/README.md @@ -448,11 +448,11 @@ Hosted users who want standard-library interoperability can opt in: 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`. -Generated immutable `string` field accessors also return `std::string_view` -under this opt-in, so hosted code can pass string fields directly to +When `PROTOCYTE_ENABLE_STD_STRING_VIEW` is set to a nonzero value, the runtime +includes `` and both `::protocyte::Span` / `Span` +and `::protocyte::String` are implicitly convertible to `std::string_view`. +Generated immutable `string` field accessors also return `std::string_view` under +this opt-in, so hosted code can pass string fields directly to standard-library APIs such as `std::format`. Code that does not enable the option keeps the smaller no-exception `Span` accessor surface. diff --git a/smoke/generated/compat.protocyte.hpp b/smoke/generated/compat.protocyte.hpp index ad29399..10bd441 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_ = {}; } -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW ::std::string_view label() const noexcept { return label_.view(); } #else ::protocyte::Span label() const noexcept { return label_.view(); } @@ -656,7 +656,7 @@ namespace protocyte_smoke::test::compat { } constexpr void clear_f_double() noexcept { f_double_ = {}; } -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW ::std::string_view f_string() const noexcept { return f_string_.view(); } #else ::protocyte::Span f_string() const noexcept { return f_string_.view(); } @@ -749,7 +749,7 @@ namespace protocyte_smoke::test::compat { constexpr bool has_oneof_string() const noexcept { return special_oneof_case_ == Special_oneofCase::oneof_string; } -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW ::std::string_view oneof_string() const noexcept { return has_oneof_string() ? special_oneof.oneof_string.view() : ::std::string_view {}; } @@ -866,7 +866,7 @@ namespace protocyte_smoke::test::compat { has_opt_int32_ = false; } -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW ::std::string_view opt_string() const noexcept { return opt_string_.view(); } #else ::protocyte::Span opt_string() const noexcept { return opt_string_.view(); } diff --git a/smoke/generated/cross_package.protocyte.hpp b/smoke/generated/cross_package.protocyte.hpp index ed1cc9b..a4ca8fd 100644 --- a/smoke/generated/cross_package.protocyte.hpp +++ b/smoke/generated/cross_package.protocyte.hpp @@ -5,7 +5,7 @@ #include -#ifndef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if !PROTOCYTE_ENABLE_STD_STRING_VIEW #include #endif diff --git a/smoke/generated/example.protocyte.hpp b/smoke/generated/example.protocyte.hpp index 1e48a15..762750d 100644 --- a/smoke/generated/example.protocyte.hpp +++ b/smoke/generated/example.protocyte.hpp @@ -5,7 +5,7 @@ #include -#ifndef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if !PROTOCYTE_ENABLE_STD_STRING_VIEW #include #endif @@ -98,7 +98,7 @@ namespace test::ultimate { return out; } -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW ::std::string_view description() const noexcept { return description_.view(); } #else ::protocyte::Span description() const noexcept { return description_.view(); } @@ -400,7 +400,7 @@ namespace test::ultimate { return out; } -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW ::std::string_view name() const noexcept { return name_.view(); } #else ::protocyte::Span name() const noexcept { return name_.view(); } @@ -1128,7 +1128,7 @@ namespace test::ultimate { deep_oneof_case_ = Deep_oneofCase::none; } -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW ::std::string_view extreme() const noexcept { return extreme_.view(); } #else ::protocyte::Span extreme() const noexcept { return extreme_.view(); } @@ -1182,7 +1182,7 @@ namespace test::ultimate { } constexpr bool has_text() const noexcept { return deep_oneof_case_ == Deep_oneofCase::text; } -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW ::std::string_view text() const noexcept { return has_text() ? deep_oneof.text.view() : ::std::string_view {}; } #else ::protocyte::Span text() const noexcept { @@ -2315,7 +2315,7 @@ namespace test::ultimate { } constexpr void clear_f_bool() noexcept { f_bool_ = {}; } -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW ::std::string_view f_string() const noexcept { return f_string_.view(); } #else ::protocyte::Span f_string() const noexcept { return f_string_.view(); } @@ -2421,7 +2421,7 @@ namespace test::ultimate { constexpr bool has_oneof_string() const noexcept { return special_oneof_case_ == Special_oneofCase::oneof_string; } -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW ::std::string_view oneof_string() const noexcept { return has_oneof_string() ? special_oneof.oneof_string.view() : ::std::string_view {}; } @@ -2777,7 +2777,7 @@ namespace test::ultimate { has_opt_int32_ = false; } -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW ::std::string_view opt_string() const noexcept { return opt_string_.view(); } #else ::protocyte::Span opt_string() const noexcept { return opt_string_.view(); } @@ -6095,7 +6095,7 @@ namespace test::ultimate { return out; } -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW ::std::string_view tag() const noexcept { return tag_.view(); } #else ::protocyte::Span tag() const noexcept { return tag_.view(); } diff --git a/smoke/generated/protocyte/runtime/runtime.hpp b/smoke/generated/protocyte/runtime/runtime.hpp index 314c868..d1ecf12 100644 --- a/smoke/generated/protocyte/runtime/runtime.hpp +++ b/smoke/generated/protocyte/runtime/runtime.hpp @@ -11,7 +11,7 @@ #include #include -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW #include #endif @@ -1269,7 +1269,7 @@ 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 +#if PROTOCYTE_ENABLE_STD_STRING_VIEW constexpr operator ::std::string_view() const noexcept requires(::std::same_as<::std::remove_cv_t, char>) { @@ -2869,7 +2869,7 @@ namespace protocyte { 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 +#if PROTOCYTE_ENABLE_STD_STRING_VIEW operator ::std::string_view() const noexcept { return view(); } #endif void clear() noexcept { bytes_.clear(); } diff --git a/smoke/src/kernel_driver_smoke.cpp b/smoke/src/kernel_driver_smoke.cpp index 6f44f0a..db5523c 100644 --- a/smoke/src/kernel_driver_smoke.cpp +++ b/smoke/src/kernel_driver_smoke.cpp @@ -24,7 +24,7 @@ void __cdecl operator delete[](void *ptr) noexcept { ::operator delete(ptr); } void __cdecl operator delete[](void *ptr, size_t) noexcept { ::operator delete(ptr); } #endif -#if defined(PROTOCYTE_ENABLE_STD_STRING_VIEW) && defined(_DEBUG) +#if PROTOCYTE_ENABLE_STD_STRING_VIEW && defined(_DEBUG) // MSVC's debug STL imports these CRT assertion hooks through __imp_* data symbols. __declspec(noreturn) void protocyte_debug_crt_shim_bugcheck(const char *symbol_name) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, diff --git a/src/protocyte/cpp.py b/src/protocyte/cpp.py index 14a5047..3cfe558 100644 --- a/src/protocyte/cpp.py +++ b/src/protocyte/cpp.py @@ -210,7 +210,7 @@ def generate_header(file_model: FileModel, options: GeneratorOptions) -> str: w.line(f"#include <{options.runtime_prefix}/runtime.hpp>") extra_includes: list[str] = [] if _file_uses_string_view(file_model): - extra_includes.extend(["#ifndef PROTOCYTE_ENABLE_STD_STRING_VIEW", "#include ", "#endif"]) + extra_includes.extend(["#if !PROTOCYTE_ENABLE_STD_STRING_VIEW", "#include ", "#endif"]) for dependency in sorted(file_model.dependencies): extra_includes.append(f'#include "{_include_path(dependency, options)}"') if extra_includes: @@ -1979,7 +1979,7 @@ def _emit_string_view_accessor( ) -> None: if span_expr is None: span_expr = string_view_expr - w.line("#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW") + w.line("#if PROTOCYTE_ENABLE_STD_STRING_VIEW") w.line(f"::std::string_view {name}() const noexcept {{ return {string_view_expr}; }}") w.line("#else") w.line(f"::protocyte::Span {name}() const noexcept {{ return {span_expr}; }}") diff --git a/src/protocyte/runtime/runtime.hpp b/src/protocyte/runtime/runtime.hpp index 314c868..d1ecf12 100644 --- a/src/protocyte/runtime/runtime.hpp +++ b/src/protocyte/runtime/runtime.hpp @@ -11,7 +11,7 @@ #include #include -#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_STRING_VIEW #include #endif @@ -1269,7 +1269,7 @@ 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 +#if PROTOCYTE_ENABLE_STD_STRING_VIEW constexpr operator ::std::string_view() const noexcept requires(::std::same_as<::std::remove_cv_t, char>) { @@ -2869,7 +2869,7 @@ namespace protocyte { 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 +#if PROTOCYTE_ENABLE_STD_STRING_VIEW operator ::std::string_view() const noexcept { return view(); } #endif void clear() noexcept { bytes_.clear(); } diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 66d5e2e..9413529 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -56,7 +56,7 @@ def test_runtime_rejects_unmatched_end_group_in_skip_field() -> None: def test_kernel_smoke_provides_debug_string_view_crt_shims() -> None: source = (Path(__file__).resolve().parents[1] / "smoke" / "src" / "kernel_driver_smoke.cpp").read_text() - assert "#if defined(PROTOCYTE_ENABLE_STD_STRING_VIEW) && defined(_DEBUG)" in source + assert "#if PROTOCYTE_ENABLE_STD_STRING_VIEW && defined(_DEBUG)" in source assert "__imp__invoke_watson" in source assert "__imp__CrtDbgReport" in source assert "DbgPrintEx" in source @@ -183,7 +183,8 @@ 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 "#if PROTOCYTE_ENABLE_STD_STRING_VIEW\n#include \n#endif" in runtime_header + assert "#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW" not 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 @@ -690,7 +691,7 @@ def test_generated_header_contains_expected_field_api() -> None: assert "bool has_opt_name() const noexcept" in header assert "#include " not in header assert ( - " #ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW\n" + " #if PROTOCYTE_ENABLE_STD_STRING_VIEW\n" " ::std::string_view opt_name() const noexcept { return opt_name_.view(); }\n" " #else\n" " ::protocyte::Span opt_name() const noexcept { return opt_name_.view(); }\n" @@ -1058,7 +1059,7 @@ def test_generated_header_emits_constants_and_array_storage() -> None: assert ( "#include \n\n" - "#ifndef PROTOCYTE_ENABLE_STD_STRING_VIEW\n" + "#if !PROTOCYTE_ENABLE_STD_STRING_VIEW\n" "#include \n" "#endif" ) in header @@ -1397,7 +1398,7 @@ def test_generated_header_emits_utf8_string_constants() -> None: header = next(file.content for file in response.file if file.name == "unicode.protocyte.hpp") assert ( "#include \n\n" - "#ifndef PROTOCYTE_ENABLE_STD_STRING_VIEW\n" + "#if !PROTOCYTE_ENABLE_STD_STRING_VIEW\n" "#include \n" "#endif" ) in header @@ -1446,7 +1447,7 @@ def test_generated_header_emits_tagged_union_oneofs() -> None: assert "destroy_at_(&choice.inner);" in header assert "#include " not in header assert ( - " #ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW\n" + " #if PROTOCYTE_ENABLE_STD_STRING_VIEW\n" " ::std::string_view text() const noexcept { return has_text() ? choice.text.view() : ::std::string_view{}; }\n" " #else\n" " ::protocyte::Span text() const noexcept { return has_text() ? choice.text.view() : ::protocyte::Span{}; }\n" From 4610bafa1554cedeee7aa3eab5dada2a22ed2db1 Mon Sep 17 00:00:00 2001 From: Anthony Printup <92564080+anthonyprintup@users.noreply.github.com> Date: Fri, 1 May 2026 22:09:24 +0200 Subject: [PATCH 5/5] refactor: alias generated string view return type Add protocyte::StringView as the runtime-selected string view type. Use the alias in generated immutable string accessors so string and oneof bodies are no longer duplicated across the string_view feature branch. Regenerate smoke checked outputs and update generator assertions. --- smoke/generated/compat.protocyte.hpp | 28 ++-------- smoke/generated/example.protocyte.hpp | 54 ++++--------------- smoke/generated/protocyte/runtime/runtime.hpp | 6 +++ src/protocyte/cpp.py | 14 ++--- src/protocyte/runtime/runtime.hpp | 6 +++ tests/test_plugin.py | 20 +++---- 6 files changed, 38 insertions(+), 90 deletions(-) diff --git a/smoke/generated/compat.protocyte.hpp b/smoke/generated/compat.protocyte.hpp index 10bd441..e5ffda6 100644 --- a/smoke/generated/compat.protocyte.hpp +++ b/smoke/generated/compat.protocyte.hpp @@ -65,11 +65,7 @@ namespace protocyte_smoke::test::compat { } constexpr void clear_value() noexcept { value_ = {}; } -#if PROTOCYTE_ENABLE_STD_STRING_VIEW - ::std::string_view label() const noexcept { return label_.view(); } -#else - ::protocyte::Span label() const noexcept { return label_.view(); } -#endif + ::protocyte::StringView 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) @@ -656,11 +652,7 @@ namespace protocyte_smoke::test::compat { } constexpr void clear_f_double() noexcept { f_double_ = {}; } -#if PROTOCYTE_ENABLE_STD_STRING_VIEW - ::std::string_view f_string() const noexcept { return f_string_.view(); } -#else - ::protocyte::Span f_string() const noexcept { return f_string_.view(); } -#endif + ::protocyte::StringView 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) @@ -749,15 +741,9 @@ namespace protocyte_smoke::test::compat { constexpr bool has_oneof_string() const noexcept { return special_oneof_case_ == Special_oneofCase::oneof_string; } -#if PROTOCYTE_ENABLE_STD_STRING_VIEW - ::std::string_view oneof_string() const noexcept { - return has_oneof_string() ? special_oneof.oneof_string.view() : ::std::string_view {}; + ::protocyte::StringView oneof_string() const noexcept { + return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::StringView {}; } -#else - ::protocyte::Span oneof_string() const noexcept { - return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::Span {}; - } -#endif template::protocyte::Status set_oneof_string(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) { @@ -866,11 +852,7 @@ namespace protocyte_smoke::test::compat { has_opt_int32_ = false; } -#if PROTOCYTE_ENABLE_STD_STRING_VIEW - ::std::string_view opt_string() const noexcept { return opt_string_.view(); } -#else - ::protocyte::Span opt_string() const noexcept { return opt_string_.view(); } -#endif + ::protocyte::StringView 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 762750d..05e9b0c 100644 --- a/smoke/generated/example.protocyte.hpp +++ b/smoke/generated/example.protocyte.hpp @@ -98,11 +98,7 @@ namespace test::ultimate { return out; } -#if PROTOCYTE_ENABLE_STD_STRING_VIEW - ::std::string_view description() const noexcept { return description_.view(); } -#else - ::protocyte::Span description() const noexcept { return description_.view(); } -#endif + ::protocyte::StringView 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) @@ -400,11 +396,7 @@ namespace test::ultimate { return out; } -#if PROTOCYTE_ENABLE_STD_STRING_VIEW - ::std::string_view name() const noexcept { return name_.view(); } -#else - ::protocyte::Span name() const noexcept { return name_.view(); } -#endif + ::protocyte::StringView 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) @@ -1128,11 +1120,7 @@ namespace test::ultimate { deep_oneof_case_ = Deep_oneofCase::none; } -#if PROTOCYTE_ENABLE_STD_STRING_VIEW - ::std::string_view extreme() const noexcept { return extreme_.view(); } -#else - ::protocyte::Span extreme() const noexcept { return extreme_.view(); } -#endif + ::protocyte::StringView 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) @@ -1182,13 +1170,9 @@ namespace test::ultimate { } constexpr bool has_text() const noexcept { return deep_oneof_case_ == Deep_oneofCase::text; } -#if PROTOCYTE_ENABLE_STD_STRING_VIEW - ::std::string_view text() const noexcept { return has_text() ? deep_oneof.text.view() : ::std::string_view {}; } -#else - ::protocyte::Span text() const noexcept { - return has_text() ? deep_oneof.text.view() : ::protocyte::Span {}; + ::protocyte::StringView text() const noexcept { + return has_text() ? deep_oneof.text.view() : ::protocyte::StringView {}; } -#endif template::protocyte::Status set_text(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) { @@ -2315,11 +2299,7 @@ namespace test::ultimate { } constexpr void clear_f_bool() noexcept { f_bool_ = {}; } -#if PROTOCYTE_ENABLE_STD_STRING_VIEW - ::std::string_view f_string() const noexcept { return f_string_.view(); } -#else - ::protocyte::Span f_string() const noexcept { return f_string_.view(); } -#endif + ::protocyte::StringView 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) @@ -2421,15 +2401,9 @@ namespace test::ultimate { constexpr bool has_oneof_string() const noexcept { return special_oneof_case_ == Special_oneofCase::oneof_string; } -#if PROTOCYTE_ENABLE_STD_STRING_VIEW - ::std::string_view oneof_string() const noexcept { - return has_oneof_string() ? special_oneof.oneof_string.view() : ::std::string_view {}; + ::protocyte::StringView oneof_string() const noexcept { + return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::StringView {}; } -#else - ::protocyte::Span oneof_string() const noexcept { - return has_oneof_string() ? special_oneof.oneof_string.view() : ::protocyte::Span {}; - } -#endif template::protocyte::Status set_oneof_string(const Value &value) noexcept requires(::protocyte::ByteSpanSource && !::protocyte::TextSource) { @@ -2777,11 +2751,7 @@ namespace test::ultimate { has_opt_int32_ = false; } -#if PROTOCYTE_ENABLE_STD_STRING_VIEW - ::std::string_view opt_string() const noexcept { return opt_string_.view(); } -#else - ::protocyte::Span opt_string() const noexcept { return opt_string_.view(); } -#endif + ::protocyte::StringView 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; @@ -6095,11 +6065,7 @@ namespace test::ultimate { return out; } -#if PROTOCYTE_ENABLE_STD_STRING_VIEW - ::std::string_view tag() const noexcept { return tag_.view(); } -#else - ::protocyte::Span tag() const noexcept { return tag_.view(); } -#endif + ::protocyte::StringView 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 d1ecf12..187b041 100644 --- a/smoke/generated/protocyte/runtime/runtime.hpp +++ b/smoke/generated/protocyte/runtime/runtime.hpp @@ -1316,6 +1316,12 @@ namespace protocyte { requires(!DataSizeSpanSource && PointerSpanSource) Span(Range &) -> Span<::std::remove_pointer_t>>; +#if PROTOCYTE_ENABLE_STD_STRING_VIEW + using StringView = ::std::string_view; +#else + using StringView = Span; +#endif + template concept SpanSource = requires(T &value) { Span {value}; } || requires(const T &value) { Span {value}; }; diff --git a/src/protocyte/cpp.py b/src/protocyte/cpp.py index 3cfe558..9c6e345 100644 --- a/src/protocyte/cpp.py +++ b/src/protocyte/cpp.py @@ -885,8 +885,7 @@ def _emit_oneof_accessors(w: CppWriter, item: FieldModel, options: GeneratorOpti _emit_string_view_accessor( w, item.cpp_name, - f"has_{item.cpp_name}() ? {_member(item)}.view() : ::std::string_view{{}}", - f"has_{item.cpp_name}() ? {_member(item)}.view() : ::protocyte::Span{{}}", + f"has_{item.cpp_name}() ? {_member(item)}.view() : ::protocyte::StringView{{}}", ) else: view_type = "::protocyte::Span" @@ -1974,16 +1973,9 @@ def _runtime_scalar_type(cpp_type: str) -> str: def _emit_string_view_accessor( w: CppWriter, name: str, - string_view_expr: str, - span_expr: str | None = None, + expr: str, ) -> None: - if span_expr is None: - span_expr = string_view_expr - w.line("#if PROTOCYTE_ENABLE_STD_STRING_VIEW") - w.line(f"::std::string_view {name}() const noexcept {{ return {string_view_expr}; }}") - w.line("#else") - w.line(f"::protocyte::Span {name}() const noexcept {{ return {span_expr}; }}") - w.line("#endif") + w.line(f"::protocyte::StringView {name}() const noexcept {{ return {expr}; }}") def _file_uses_string_view(file_model: FileModel) -> bool: diff --git a/src/protocyte/runtime/runtime.hpp b/src/protocyte/runtime/runtime.hpp index d1ecf12..187b041 100644 --- a/src/protocyte/runtime/runtime.hpp +++ b/src/protocyte/runtime/runtime.hpp @@ -1316,6 +1316,12 @@ namespace protocyte { requires(!DataSizeSpanSource && PointerSpanSource) Span(Range &) -> Span<::std::remove_pointer_t>>; +#if PROTOCYTE_ENABLE_STD_STRING_VIEW + using StringView = ::std::string_view; +#else + using StringView = Span; +#endif + template concept SpanSource = requires(T &value) { Span {value}; } || requires(const T &value) { Span {value}; }; diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 9413529..a55075b 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -211,6 +211,7 @@ def test_runtime_byte_containers_use_bulk_copy_helpers() -> None: assert "requires(::std::same_as<::std::remove_cv_t, char>)" in span_body assert "return ::std::string_view {data_, size_};" in span_body assert "data_ == nullptr ? ::std::string_view {}" not in span_body + assert "#if PROTOCYTE_ENABLE_STD_STRING_VIEW\n using StringView = ::std::string_view;\n#else\n using StringView = Span;\n#endif" in runtime_header 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 @@ -690,13 +691,9 @@ def test_generated_header_contains_expected_field_api() -> None: assert "namespace demo {" in header assert "bool has_opt_name() const noexcept" in header assert "#include " not in header - assert ( - " #if PROTOCYTE_ENABLE_STD_STRING_VIEW\n" - " ::std::string_view opt_name() const noexcept { return opt_name_.view(); }\n" - " #else\n" - " ::protocyte::Span opt_name() const noexcept { return opt_name_.view(); }\n" - " #endif" - ) in header + assert " ::protocyte::StringView opt_name() const noexcept { return opt_name_.view(); }" in header + assert "::std::string_view opt_name()" not in header + assert "::protocyte::Span opt_name()" not 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 @@ -1447,12 +1444,11 @@ def test_generated_header_emits_tagged_union_oneofs() -> None: assert "destroy_at_(&choice.inner);" in header assert "#include " not in header assert ( - " #if PROTOCYTE_ENABLE_STD_STRING_VIEW\n" - " ::std::string_view text() const noexcept { return has_text() ? choice.text.view() : ::std::string_view{}; }\n" - " #else\n" - " ::protocyte::Span text() const noexcept { return has_text() ? choice.text.view() : ::protocyte::Span{}; }\n" - " #endif" + " ::protocyte::StringView text() const noexcept { " + "return has_text() ? choice.text.view() : ::protocyte::StringView{}; }" ) in header + assert "::std::string_view text()" not in header + assert "::protocyte::Span text()" not in header assert "union ChoiceStorage {" in header assert "ChoiceStorage() noexcept {}" in header assert "~ChoiceStorage() noexcept {}" in header