From 6920f22ab8b93891b6b5af7204eb069e2c1045bf Mon Sep 17 00:00:00 2001 From: Ivan FB Date: Mon, 11 May 2026 21:52:00 +0200 Subject: [PATCH] =?UTF-8?q?fix=20nlohmann=20namespace=20injection=20of=20o?= =?UTF-8?q?ptional=20adapters=20is=20ODR-fragile=20(cpp.nim:66=E2=80=9378)?= =?UTF-8?q?.=20Every=20header=20that=20does=20this=20races=20every=20other?= =?UTF-8?q?=20header=20that=20does=20the=20same.=20In=20a=20small=20=20=20?= =?UTF-8?q?project=20it's=20invisible;=20in=20something=20the=20size=20of?= =?UTF-8?q?=20nwaku's=20consumer=20surface=20i2t=20will=20eventually=20col?= =?UTF-8?q?lide.=20Either=20gate=20behind=20a=20feature=20macro,=20or=20us?= =?UTF-8?q?e=20nlohmann's=20adl=5Fserializer=20=20=20specialization=20whic?= =?UTF-8?q?h=20is=20the=20documented=20extension=20point.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/nim_timer/cpp_bindings/nimtimer.hpp | 27 +++++++++------ ffi/codegen/cpp.nim | 36 ++++++++++++++------ 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/examples/nim_timer/cpp_bindings/nimtimer.hpp b/examples/nim_timer/cpp_bindings/nimtimer.hpp index 77af079..95f33d8 100644 --- a/examples/nim_timer/cpp_bindings/nimtimer.hpp +++ b/examples/nim_timer/cpp_bindings/nimtimer.hpp @@ -13,19 +13,26 @@ #include #include +#if !defined(NIM_FFI_NO_OPTIONAL_SERIALIZER) \ + && !defined(NIM_FFI_OPTIONAL_SERIALIZER_DEFINED_) \ + && (!defined(NLOHMANN_JSON_VERSION_MAJOR) \ + || (NLOHMANN_JSON_VERSION_MAJOR < 3) \ + || (NLOHMANN_JSON_VERSION_MAJOR == 3 && NLOHMANN_JSON_VERSION_MINOR < 12)) +#define NIM_FFI_OPTIONAL_SERIALIZER_DEFINED_ namespace nlohmann { template - void to_json(json& j, const std::optional& opt) { - if (opt) j = *opt; - else j = nullptr; - } - - template - void from_json(const json& j, std::optional& opt) { - if (j.is_null()) opt = std::nullopt; - else opt = j.get(); - } + struct adl_serializer> { + static void to_json(json& j, const std::optional& opt) { + if (opt) j = *opt; + else j = nullptr; + } + static void from_json(const json& j, std::optional& opt) { + if (j.is_null()) opt = std::nullopt; + else opt = j.get(); + } + }; } +#endif // ============================================================ // Types diff --git a/ffi/codegen/cpp.nim b/ffi/codegen/cpp.nim index 652af2f..988c500 100644 --- a/ffi/codegen/cpp.nim +++ b/ffi/codegen/cpp.nim @@ -64,19 +64,35 @@ proc generateCppHeader*( lines.add("") # ── nlohmann optional support ────────────────────────────────────────── + # Use nlohmann's documented extension point (adl_serializer specialization) + # rather than free-function to_json/from_json in namespace nlohmann -- the + # latter is ODR-fragile across libraries that all do the same. Three + # guards layer the protection: + # * NIM_FFI_NO_OPTIONAL_SERIALIZER : consumer opt-out for conflicts. + # * NIM_FFI_OPTIONAL_SERIALIZER_DEFINED_ : TU-level guard so multiple + # nim-ffi-generated headers don't redefine within one translation unit. + # * nlohmann version check : nlohmann >= 3.12 ships this specialization + # natively, so we skip to avoid an ODR violation against it. + lines.add("#if !defined(NIM_FFI_NO_OPTIONAL_SERIALIZER) \\") + lines.add(" && !defined(NIM_FFI_OPTIONAL_SERIALIZER_DEFINED_) \\") + lines.add(" && (!defined(NLOHMANN_JSON_VERSION_MAJOR) \\") + lines.add(" || (NLOHMANN_JSON_VERSION_MAJOR < 3) \\") + lines.add(" || (NLOHMANN_JSON_VERSION_MAJOR == 3 && NLOHMANN_JSON_VERSION_MINOR < 12))") + lines.add("#define NIM_FFI_OPTIONAL_SERIALIZER_DEFINED_") lines.add("namespace nlohmann {") lines.add(" template") - lines.add(" void to_json(json& j, const std::optional& opt) {") - lines.add(" if (opt) j = *opt;") - lines.add(" else j = nullptr;") - lines.add(" }") - lines.add("") - lines.add(" template") - lines.add(" void from_json(const json& j, std::optional& opt) {") - lines.add(" if (j.is_null()) opt = std::nullopt;") - lines.add(" else opt = j.get();") - lines.add(" }") + lines.add(" struct adl_serializer> {") + lines.add(" static void to_json(json& j, const std::optional& opt) {") + lines.add(" if (opt) j = *opt;") + lines.add(" else j = nullptr;") + lines.add(" }") + lines.add(" static void from_json(const json& j, std::optional& opt) {") + lines.add(" if (j.is_null()) opt = std::nullopt;") + lines.add(" else opt = j.get();") + lines.add(" }") + lines.add(" };") lines.add("}") + lines.add("#endif") lines.add("") # ── Types ──────────────────────────────────────────────────────────────────