From 1e9b85262a5d5effd210ded412cb2c74089879e2 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 13 Oct 2026 11:14:24 +0200 Subject: [PATCH] Export the remaining client-facing headers from json.hpp and check clients with IWYU Besides json_fwd.hpp, include analyzers also told clients of to include detail/json_ref.hpp (brace initialization), json_literals.hpp (_json, _json_pointer), adl_serializer.hpp, detail/macro_scope.hpp (NLOHMANN_DEFINE_*) and detail/abi_macros.hpp. Mark those includes as exported, too. ci_single_binaries only checked each header on its own, so it could not notice this. It now also runs IWYU and clang-tidy's misc-include-cleaner on small client programs in tests/iwyu_consumer, which fail on develop. With the exports, json_literals.hpp no longer gets the json_fwd.hpp suggestion that kept it out of the IWYU gate, so it is gated again. Signed-off-by: Niels Lohmann --- cmake/ci.cmake | 26 ++++++++---- include/nlohmann/json.hpp | 15 ++++--- single_include/nlohmann/json.hpp | 15 ++++--- tests/iwyu_consumer/conversions.cpp | 62 +++++++++++++++++++++++++++++ tests/iwyu_consumer/literals.cpp | 19 +++++++++ tests/iwyu_consumer/values.cpp | 30 ++++++++++++++ 6 files changed, 150 insertions(+), 17 deletions(-) create mode 100644 tests/iwyu_consumer/conversions.cpp create mode 100644 tests/iwyu_consumer/literals.cpp create mode 100644 tests/iwyu_consumer/values.cpp diff --git a/cmake/ci.cmake b/cmake/ci.cmake index 5affec6c5..cc8aaf620 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -610,9 +610,8 @@ foreach(SRC_FILE ${SRC_FILES}) add_executable(single_${RELATIVE_SRC_FILE} EXCLUDE_FROM_ALL ${PROJECT_BINARY_DIR}/src_single/${RELATIVE_SRC_FILE}.cpp) target_include_directories(single_${RELATIVE_SRC_FILE} PRIVATE ${PROJECT_SOURCE_DIR}/include) target_compile_features(single_${RELATIVE_SRC_FILE} PRIVATE cxx_std_11) - if(RELATIVE_SRC_FILE STREQUAL "json" OR RELATIVE_SRC_FILE STREQUAL "json_literals") - # see below: report the diagnostics of json.hpp and json_literals.hpp without --error, so they - # do not fail the build + if(RELATIVE_SRC_FILE STREQUAL "json") + # see below: report the diagnostics of json.hpp without --error, so they do not fail the build set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${IWYU_TOOL} -Xiwyu --max_line_length=300) else() set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") @@ -626,18 +625,31 @@ foreach(SRC_FILE ${SRC_FILES}) # reporting its diagnostics (informational, via CXX_INCLUDE_WHAT_YOU_USE above) but exclude it # from the hard gate below so a fresh IWYU/compiler combination does not fail this target on a # nondeterministic suggestion for a header that already re-exports everything on purpose. - # json_literals.hpp and json.hpp include each other on purpose (json.hpp includes it at its end - # unless JSON_NO_AUTOMATIC_UDLS is defined), and IWYU, not following the cycle, suggests replacing - # json.hpp with json_fwd.hpp although the literals need the complete basic_json; exclude it, too. - if(NOT RELATIVE_SRC_FILE STREQUAL "json" AND NOT RELATIVE_SRC_FILE STREQUAL "json_literals") + if(NOT RELATIVE_SRC_FILE STREQUAL "json") list(APPEND single_binaries_tus src_single/${RELATIVE_SRC_FILE}.cpp) endif() endforeach() +# The checks above look at each header on its own. Client code that includes only +# must not be told by include analyzers to include another nlohmann header for what it uses +# (issue #5812): json.hpp marks those includes "IWYU pragma: export", and both IWYU and clang-tidy's +# misc-include-cleaner (disabled in .clang-tidy for the library itself) check these clients. +file(GLOB CONSUMER_FILES ${PROJECT_SOURCE_DIR}/tests/iwyu_consumer/*.cpp) +foreach(CONSUMER_FILE ${CONSUMER_FILES}) + get_filename_component(CONSUMER_NAME "${CONSUMER_FILE}" NAME_WE) + add_executable(iwyu_consumer_${CONSUMER_NAME} EXCLUDE_FROM_ALL ${CONSUMER_FILE}) + target_include_directories(iwyu_consumer_${CONSUMER_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/include) + target_compile_features(iwyu_consumer_${CONSUMER_NAME} PRIVATE cxx_std_11) + list(APPEND single_binaries iwyu_consumer_${CONSUMER_NAME}) + list(APPEND single_binaries_tus ${CONSUMER_FILE}) +endforeach() + if(IWYU_TOOL_PY) add_custom_target(ci_single_binaries DEPENDS ${single_binaries} COMMAND ${IWYU_TOOL_PY} -p ${PROJECT_BINARY_DIR} ${single_binaries_tus} -- ${iwyu_options} + COMMAND ${CLANG_TIDY_TOOL} -p ${PROJECT_BINARY_DIR} --checks=-*,misc-include-cleaner --warnings-as-errors=* ${CONSUMER_FILES} + VERBATIM COMMENT "Check if headers are self-contained" ) else() diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index d2eb80353..3bad024f1 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -42,9 +42,14 @@ // each of these, not only the forward declarations from json_fwd.hpp, so IWYU's suggestion to // drop them (nothing in this file otherwise names the type) would break every downstream // translation unit that relies on basic_json<>'s defaults actually being usable. -#include // IWYU pragma: keep +// export: client code names what these headers declare (nlohmann::json and ordered_json from +// json_fwd.hpp, adl_serializer for specializations, json_ref behind brace initialization, the +// NLOHMANN_DEFINE_* and NLOHMANN_JSON_NAMESPACE_* macros, and the literals at the end of this file), and include analyzers must +// attribute it to this header, not tell clients to include the internal ones (issue #5812). +// tests/iwyu_consumer checks this. +#include // IWYU pragma: export #include -#include +#include // IWYU pragma: export #include // IWYU pragma: keep #include // IWYU pragma: keep #include @@ -61,8 +66,8 @@ #include #include #include -#include -#include +#include // IWYU pragma: export +#include // IWYU pragma: export #include #include #include @@ -7795,7 +7800,7 @@ struct formatter // NOLINT(cert-dcl58-c // where needed. #ifndef JSON_NO_AUTOMATIC_UDLS // NOLINTNEXTLINE(misc-header-include-cycle): json_literals.hpp includes this header - #include + #include // IWYU pragma: export #endif #endif // INCLUDE_NLOHMANN_JSON_HPP_ diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index c20b295f8..0692997ec 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -42,6 +42,11 @@ // each of these, not only the forward declarations from json_fwd.hpp, so IWYU's suggestion to // drop them (nothing in this file otherwise names the type) would break every downstream // translation unit that relies on basic_json<>'s defaults actually being usable. +// export: client code names what these headers declare (nlohmann::json and ordered_json from +// json_fwd.hpp, adl_serializer for specializations, json_ref behind brace initialization, the +// NLOHMANN_DEFINE_* and NLOHMANN_JSON_NAMESPACE_* macros, and the literals at the end of this file), and include analyzers must +// attribute it to this header, not tell clients to include the internal ones (issue #5812). +// tests/iwyu_consumer checks this. // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ @@ -7533,7 +7538,7 @@ struct adl_serializer }; NLOHMANN_JSON_NAMESPACE_END -// IWYU pragma: keep +// IWYU pragma: export // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ @@ -7641,7 +7646,7 @@ class byte_container_with_subtype : public BinaryType NLOHMANN_JSON_NAMESPACE_END // #include - +// IWYU pragma: export // #include // IWYU pragma: keep // #include @@ -21410,9 +21415,9 @@ class json_ref } // namespace detail NLOHMANN_JSON_NAMESPACE_END - +// IWYU pragma: export // #include - +// IWYU pragma: export // #include // #include @@ -36209,7 +36214,7 @@ NLOHMANN_JSON_NAMESPACE_END #undef NLOHMANN_JSON_LITERAL_OPERATOR #endif // INCLUDE_NLOHMANN_JSON_LITERALS_HPP_ - +// IWYU pragma: export #endif #endif // INCLUDE_NLOHMANN_JSON_HPP_ diff --git a/tests/iwyu_consumer/conversions.cpp b/tests/iwyu_consumer/conversions.cpp new file mode 100644 index 000000000..599da67b7 --- /dev/null +++ b/tests/iwyu_consumer/conversions.cpp @@ -0,0 +1,62 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +// See values.cpp: the conversion macros and nlohmann::adl_serializer must be available through +// alone. NLOHMANN_JSON_SERIALIZE_ENUM (from the same header as +// NLOHMANN_DEFINE_TYPE_INTRUSIVE) is left out: IWYU attributes the basic_json comparison in its +// expansion to an unrelated standard header, the ambiguity described for json.hpp in cmake/ci.cmake. + +#include + +#include + +namespace ns +{ +struct person +{ + std::string name; + int age; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(person, name, age) +}; + +struct legacy +{ + int value; +}; +} // namespace ns + +namespace nlohmann +{ +template<> +struct adl_serializer +{ + static void to_json(json& j, const ns::legacy& l) + { + j = l.value; + } + + static void from_json(const json& j, ns::legacy& l) + { + l.value = j.get(); + } +}; +} // namespace nlohmann + +int main() +{ + ns::person ada; + ada.name = "Ada"; + ada.age = 36; + ns::legacy one; + one.value = 1; + + const nlohmann::json j = ada; + const auto p = j.get(); + const auto l = nlohmann::json(one).get(); + return p.age - 36 + l.value - 1; +} diff --git a/tests/iwyu_consumer/literals.cpp b/tests/iwyu_consumer/literals.cpp new file mode 100644 index 000000000..7aaf58d79 --- /dev/null +++ b/tests/iwyu_consumer/literals.cpp @@ -0,0 +1,19 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +// See values.cpp: the user-defined literals must be available through alone. + +#include + +using namespace nlohmann::literals; // NOLINT(google-build-using-namespace) + +int main() +{ + const auto j = R"({"a": [1, 2]})"_json; + return j.at("/a/1"_json_pointer).get() - 2; +} diff --git a/tests/iwyu_consumer/values.cpp b/tests/iwyu_consumer/values.cpp new file mode 100644 index 000000000..1f6d00b76 --- /dev/null +++ b/tests/iwyu_consumer/values.cpp @@ -0,0 +1,30 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +// Uses the library the way a client does, including only . ci_single_binaries +// runs include-what-you-use and clang-tidy's misc-include-cleaner on this file and fails if either +// asks for another nlohmann header, which would mean json.hpp does not export it (issue #5812). + +#include +#include +#include + +#include + +int main() +{ + // brace initialization (nlohmann::json, detail::json_ref) + nlohmann::json j = {{"pi", 3.141}, {"list", {1, 0, 2}}}; + const nlohmann::ordered_json o = {{"b", 1}, {"a", 2}}; + + j["parsed"] = nlohmann::json::parse(R"({"happy": true})"); + j["binary"] = nlohmann::json::binary(std::vector {0xCA, 0xFE}, 42); + + const std::string s = j.dump() + o.dump(); + return s.empty() ? 1 : 0; +}