From 4ce130af4e28839182a1363fb02aad47dbe71f61 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 27 Jul 2026 12:49:55 +0000 Subject: [PATCH] Fix CI failures in from_chars locale handling (PR #5237) Three issues in the extended-locale number parsing were breaking CI: - On Windows, _create_locale() was called with LC_ALL_MASK, a POSIX-only constant that doesn't exist in the Windows CRT; it needs LC_ALL there. - `::isspace_l`/`::_isspace_l` were called with a leading `::`, but on glibc, mingw-w64 and some other C libraries these are macros that expand to a parenthesized expression, so `::` followed by the expansion is a syntax error ("expected unqualified-id"). Dropping the `::` fixes this on all affected toolchains (gcc/clang on Linux, nvcc, mingw). - The Windows implementation assumed the full `__l` family is always available, but some MinGW toolchains only provide a subset (e.g. missing `_strtoll_l`/`_strtoull_l`/`_strtof_l`/`_strtold_l`). The extended-locale fast path is now only used for genuine MSVC (including clang-cl, which mimics the MSVC ABI/CRT); other Windows toolchains fall back to the locale-dependent standard functions. Also fixes a clang-tidy hicpp-named-parameter/readability-named-parameter warning-as-error on two unnamed `init_val_t` test parameters. Verified locally with gcc/clang on Linux, clang++ in C++20 mode, and mingw-w64 (both g++ and clang targeting x86_64-w64-mingw32). --- .../detail/conversions/from_chars.hpp | 89 ++++++++++++++----- single_include/nlohmann/json.hpp | 89 ++++++++++++++----- tests/src/unit-from_chars.cpp | 4 +- 3 files changed, 136 insertions(+), 46 deletions(-) diff --git a/include/nlohmann/detail/conversions/from_chars.hpp b/include/nlohmann/detail/conversions/from_chars.hpp index 728c58908..46bd63a70 100644 --- a/include/nlohmann/detail/conversions/from_chars.hpp +++ b/include/nlohmann/detail/conversions/from_chars.hpp @@ -190,6 +190,58 @@ inline from_chars_result from_chars(const char* first, const char* last, T& valu #if defined(_WIN32) +/*! +@brief Extended locale functions on Windows + +A trait object which provides wrappers for isspace and the strto* family of +functions, based on the platform's support for extended locale APIs. + +This is the Windows implementation. The primary class template definition +falls back to the standard locale-dependent functions, which is used on +MinGW, whose C runtime only provides an incomplete subset of the +`__l` family (e.g., missing `_strtof_l`/`_strtold_l` depending on +the toolchain version). A specialization for genuine MSVC (including +clang-cl, which mimics the MSVC ABI and CRT) is used instead, as the full +`__l` family has reliably been available there since at least +Visual Studio 2005. +*/ +template +struct extended_locale_traits +{ + static constexpr bool is_locale_independent = false; + + JSON_HEDLEY_PURE + static char get_decimal_point() noexcept + { + const auto* loc = localeconv(); + JSON_ASSERT(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); + } + + static int isspace(int c) noexcept + { + return std::isspace(c); + } + + // NOLINTNEXTLINE(runtime/int) + static long long strtoll(const char* str, char** endptr) noexcept + { + return std::strtoll(str, endptr, 10); + } + + // NOLINTNEXTLINE(runtime/int) + static unsigned long long strtoull(const char* str, char** endptr) noexcept + { + return std::strtoull(str, endptr, 10); + } + + static constexpr float(&strtof)(const char*, char**) = std::strtof; + static constexpr double(&strtod)(const char*, char**) = std::strtod; + static constexpr long double(&strtold)(const char*, char**) = std::strtold; +}; + +#if defined(_MSC_VER) + /*! @brief Get a pointer to a globally shared instance of the "C" locale on Windows @@ -208,58 +260,51 @@ inline _locale_t get_c_locale_t() noexcept } }; using LocaleUPtr = std::unique_ptr::type, LocaleTDeleter>; - static const LocaleUPtr c_locale = LocaleUPtr{::_create_locale(LC_ALL_MASK, "C"), LocaleTDeleter{}}; + static const LocaleUPtr c_locale = LocaleUPtr{::_create_locale(LC_ALL, "C"), LocaleTDeleter{}}; return c_locale.get(); } -/*! -@brief Extended locale functions on Windows - -A trait object which provides wrappers for isspace and the strto* family of -functions, based on the platform's support for extended locale APIs. - -This is the Windows implementation where extended locale support is always -available through the `__l` variants. -*/ -template -struct extended_locale_traits +template +struct extended_locale_traits { static constexpr bool is_locale_independent = true; static constexpr char(&get_decimal_point)() = get_locale_independent_decimal_point; static int isspace(int c) noexcept { - return ::_isspace_l(c, get_c_locale_t()); + return _isspace_l(c, get_c_locale_t()); } // NOLINTNEXTLINE(runtime/int) static long long strtoll(const char* str, char** endptr) noexcept { - return ::_strtoll_l(str, endptr, 10, get_c_locale_t()); + return _strtoll_l(str, endptr, 10, get_c_locale_t()); } // NOLINTNEXTLINE(runtime/int) static unsigned long long strtoull(const char* str, char** endptr) noexcept { - return ::_strtoull_l(str, endptr, 10, get_c_locale_t()); + return _strtoull_l(str, endptr, 10, get_c_locale_t()); } - static float strtof(const char* str, char** str_end) noexcept + static float strtof(T str, char** str_end) { - return ::_strtof_l(str, str_end, get_c_locale_t()); + return _strtof_l(str, str_end, get_c_locale_t()); } - static double strtod(const char* str, char** str_end) noexcept + static double strtod(T str, char** str_end) { - return ::_strtod_l(str, str_end, get_c_locale_t()); + return _strtod_l(str, str_end, get_c_locale_t()); } - static long double strtold(const char* str, char** str_end) noexcept + static long double strtold(T str, char** str_end) { - return ::_strtold_l(str, str_end, get_c_locale_t()); + return _strtold_l(str, str_end, get_c_locale_t()); } }; +#endif + #else /*! @@ -360,7 +405,7 @@ struct extended_locale_traits_l` family (e.g., missing `_strtof_l`/`_strtold_l` depending on +the toolchain version). A specialization for genuine MSVC (including +clang-cl, which mimics the MSVC ABI and CRT) is used instead, as the full +`__l` family has reliably been available there since at least +Visual Studio 2005. +*/ +template +struct extended_locale_traits +{ + static constexpr bool is_locale_independent = false; + + JSON_HEDLEY_PURE + static char get_decimal_point() noexcept + { + const auto* loc = localeconv(); + JSON_ASSERT(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); + } + + static int isspace(int c) noexcept + { + return std::isspace(c); + } + + // NOLINTNEXTLINE(runtime/int) + static long long strtoll(const char* str, char** endptr) noexcept + { + return std::strtoll(str, endptr, 10); + } + + // NOLINTNEXTLINE(runtime/int) + static unsigned long long strtoull(const char* str, char** endptr) noexcept + { + return std::strtoull(str, endptr, 10); + } + + static constexpr float(&strtof)(const char*, char**) = std::strtof; + static constexpr double(&strtod)(const char*, char**) = std::strtod; + static constexpr long double(&strtold)(const char*, char**) = std::strtold; +}; + +#if defined(_MSC_VER) + /*! @brief Get a pointer to a globally shared instance of the "C" locale on Windows @@ -7924,58 +7976,51 @@ inline _locale_t get_c_locale_t() noexcept } }; using LocaleUPtr = std::unique_ptr::type, LocaleTDeleter>; - static const LocaleUPtr c_locale = LocaleUPtr{::_create_locale(LC_ALL_MASK, "C"), LocaleTDeleter{}}; + static const LocaleUPtr c_locale = LocaleUPtr{::_create_locale(LC_ALL, "C"), LocaleTDeleter{}}; return c_locale.get(); } -/*! -@brief Extended locale functions on Windows - -A trait object which provides wrappers for isspace and the strto* family of -functions, based on the platform's support for extended locale APIs. - -This is the Windows implementation where extended locale support is always -available through the `__l` variants. -*/ -template -struct extended_locale_traits +template +struct extended_locale_traits { static constexpr bool is_locale_independent = true; static constexpr char(&get_decimal_point)() = get_locale_independent_decimal_point; static int isspace(int c) noexcept { - return ::_isspace_l(c, get_c_locale_t()); + return _isspace_l(c, get_c_locale_t()); } // NOLINTNEXTLINE(runtime/int) static long long strtoll(const char* str, char** endptr) noexcept { - return ::_strtoll_l(str, endptr, 10, get_c_locale_t()); + return _strtoll_l(str, endptr, 10, get_c_locale_t()); } // NOLINTNEXTLINE(runtime/int) static unsigned long long strtoull(const char* str, char** endptr) noexcept { - return ::_strtoull_l(str, endptr, 10, get_c_locale_t()); + return _strtoull_l(str, endptr, 10, get_c_locale_t()); } - static float strtof(const char* str, char** str_end) noexcept + static float strtof(T str, char** str_end) { - return ::_strtof_l(str, str_end, get_c_locale_t()); + return _strtof_l(str, str_end, get_c_locale_t()); } - static double strtod(const char* str, char** str_end) noexcept + static double strtod(T str, char** str_end) { - return ::_strtod_l(str, str_end, get_c_locale_t()); + return _strtod_l(str, str_end, get_c_locale_t()); } - static long double strtold(const char* str, char** str_end) noexcept + static long double strtold(T str, char** str_end) { - return ::_strtold_l(str, str_end, get_c_locale_t()); + return _strtold_l(str, str_end, get_c_locale_t()); } }; +#endif + #else /*! @@ -8076,7 +8121,7 @@ struct extended_locale_traits typename std::enable_if::value, void>::type -check(std::string str, init_val_t, std::ptrdiff_t expected_ptr_offset, std::errc expected_ec) +check(std::string str, init_val_t /*unused*/, std::ptrdiff_t expected_ptr_offset, std::errc expected_ec) { T const init_value = 42; T value = init_value; @@ -89,7 +89,7 @@ check(std::string str, init_val_t, std::ptrdiff_t expected_ptr_offset, std::errc template typename std::enable_if::value, void>::type -check(std::string str, init_val_t, std::ptrdiff_t expected_ptr_offset, std::errc expected_ec) +check(std::string str, init_val_t /*unused*/, std::ptrdiff_t expected_ptr_offset, std::errc expected_ec) { { T const init_value = 42;