Compare commits

..
Author SHA1 Message Date
Niels Lohmann 14558ba011 Merge branch 'develop' into claude/issue-5066-24ba30
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-28 22:22:36 +02:00
Niels Lohmann ccb419ca64 Fix clang-tidy and clang 6 in the #5066 regression test
ci_clang_tidy asked for emplace_back instead of push_back. The
push_back is the point of the test: #5066 is about the implicit
conversion from json to the vector's value type, which emplace_back
would bypass. Silence the check on that line.

clang 5 and 6 cannot instantiate std::variant<json> from libstdc++ 10's
<variant> ("cannot cast private base class"), which broke
ci_test_compilers_clang (6). Tested with the CI images: clang 7 to 11
compile and pass, including clang 11 with libstdc++ 10. Skip the runtime
check for clang before 7; the static_assert still runs.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-28 18:22:16 +02:00
Niels Lohmann 4494890be6 Merge branch 'develop' into claude/issue-5066-24ba30
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 20:57:55 +02:00
Niels Lohmann c4469c9f82 Add regression test for converting json to std::variant<json> (#5066)
With 3.10.5, get<std::variant<json>>() was well-formed through the string
from_json overload, so the implicit conversion operator was a candidate
when converting json to std::variant<json>, and MSVC picked it over the
variant's converting constructor. The tightened constraints from #3427 and
#3604 (3.11.0) removed that path; this test guards against regressions.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 16:49:33 +02:00
5 changed files with 24 additions and 40 deletions
+19
View File
@@ -766,6 +766,25 @@ TEST_CASE("regression tests 2")
CHECK(j == k);
}
#ifdef JSON_HAS_CPP_17
SECTION("issue #5066 - MSVC converts json to std::variant<json> via the conversion operator")
{
// std::variant<json> must not be retrievable via get<>(), because otherwise the
// implicit conversion operator becomes a candidate that MSVC picks over the variant's
// converting constructor, routing a number through the string from_json overload
static_assert(!nlohmann::detail::is_detected<nlohmann::detail::get_template_function, const json&, std::variant<json>>::value,
"std::variant<json> must not be retrievable via get<>()");
// clang before 7 cannot instantiate libstdc++'s std::variant<json>
#if !(defined(__clang__) && __clang_major__ < 7)
// push_back, not emplace_back: #5066 needs the implicit conversion
// from json to the vector's value type
std::vector<std::variant<json>> v;
v.push_back(json(1)); // NOLINT(hicpp-use-emplace,modernize-use-emplace)
CHECK(std::get<0>(v[0]) == 1);
#endif
}
#endif
}
TEST_CASE("regression test - parser callback must not lose a duplicate key's prior value")
-3
View File
@@ -8,6 +8,3 @@ The following changes have been made to the code with respect to <https://github
- membership check
- made function from `_is_within`
- removed unused variable `actual_path`
- Added the optional config key `external`: include paths listed there are kept as
`#include` directives instead of being inlined (the first directive per path; the
repeated ones are commented out).
-5
View File
@@ -57,11 +57,6 @@ Python v.2.7.0 or higher is required.
amalgamation. Have a look at `test/source.c.json` and `test/include.h.json`
to see two examples.
The optional `external` list names include paths that are kept as `#include`
directives instead of being inlined, e.g. `["nlohmann/json.hpp"]` for a header
that includes another amalgamated header. Only the first directive for each
of these paths is kept; the repeated ones are commented out.
* The `-s, --source` option should specify the path to the source directory.
This is useful for supporting separate source and build directories.
+5 -23
View File
@@ -62,10 +62,6 @@ class Amalgamation(object):
return None
def __init__(self, args):
# include paths that are kept as #include directives instead of
# being inlined (e.g. a header amalgamated on its own)
self.external = []
self.included_external = []
with open(args.config, 'r') as f:
config = json.loads(f.read())
for key in config:
@@ -224,14 +220,11 @@ class TranslationUnit(object):
while include_match:
if not _is_within(include_match, skippable_contexts):
include_path = include_match.group("path")
if include_path in self.amalgamation.external:
includes.append((include_match, None))
else:
search_same_dir = include_match.group(1) == '"'
found_included_path = self.amalgamation.find_included_file(
include_path, self.file_dir if search_same_dir else None)
if found_included_path:
includes.append((include_match, found_included_path))
search_same_dir = include_match.group(1) == '"'
found_included_path = self.amalgamation.find_included_file(
include_path, self.file_dir if search_same_dir else None)
if found_included_path:
includes.append((include_match, found_included_path))
include_match = self.include_pattern.search(self.content,
include_match.end())
@@ -242,17 +235,6 @@ class TranslationUnit(object):
for include in includes:
include_match, found_included_path = include
tmp_content += self.content[prev_end:include_match.start()]
if found_included_path is None:
# an external header: keep the first directive and comment
# out the repeated ones
include_path = include_match.group("path")
if include_path in self.amalgamation.included_external:
tmp_content += "// {0}".format(include_match.group(0))
else:
self.amalgamation.included_external.append(include_path)
tmp_content += include_match.group(0)
prev_end = include_match.end()
continue
tmp_content += "// {0}\n".format(include_match.group(0))
if found_included_path not in self.amalgamation.included_files:
t = TranslationUnit(found_included_path, self.amalgamation, False)
-9
View File
@@ -1,9 +0,0 @@
{
"project": "JSON for Modern C++",
"target": "single_include/nlohmann/json_view.hpp",
"sources": [
"include/nlohmann/json_view.hpp"
],
"include_paths": ["include"],
"external": ["nlohmann/json.hpp"]
}