diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 1acc9cd5cb9..bcb33c18aa4 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -1165,11 +1165,11 @@ ftms = { // 202305 P2757R3 Type checking format args // 202306 P2637R3 Member visit // 202311 P2918R2 Runtime format strings II - // values = { - // v = 202305; - // cxxmin = 26; - // hosted = yes; - // }; + values = { + v = 202305; + cxxmin = 26; + hosted = yes; + }; // 201907 Text Formatting, Integration of chrono, printf corner cases. // 202106 std::format improvements. // 202110 Fixing locale handling in chrono formatters, generator-like types. diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h index 5cd77770e21..4d1af34bf8d 100644 --- a/libstdc++-v3/include/bits/version.h +++ b/libstdc++-v3/include/bits/version.h @@ -1304,7 +1304,12 @@ #undef __glibcxx_want_barrier #if !defined(__cpp_lib_format) -# if (__cplusplus >= 202002L) && _GLIBCXX_HOSTED +# if (__cplusplus > 202302L) && _GLIBCXX_HOSTED +# define __glibcxx_format 202305L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_format) +# define __cpp_lib_format 202305L +# endif +# elif (__cplusplus >= 202002L) && _GLIBCXX_HOSTED # define __glibcxx_format 202304L # if defined(__glibcxx_want_all) || defined(__glibcxx_want_format) # define __cpp_lib_format 202304L diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format index 2669ad8c244..6a88705ec7b 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -222,6 +222,9 @@ namespace __format inline void __failed_to_parse_format_spec() { __throw_format_error("format error: failed to parse format-spec"); } + + template class _Scanner; + } // namespace __format /// @endcond @@ -241,9 +244,8 @@ namespace __format using iterator = const_iterator; constexpr explicit - basic_format_parse_context(basic_string_view<_CharT> __fmt, - size_t __num_args = 0) noexcept - : _M_begin(__fmt.begin()), _M_end(__fmt.end()), _M_num_args(__num_args) + basic_format_parse_context(basic_string_view<_CharT> __fmt) noexcept + : _M_begin(__fmt.begin()), _M_end(__fmt.end()) { } basic_format_parse_context(const basic_format_parse_context&) = delete; @@ -283,13 +285,78 @@ namespace __format __format::__invalid_arg_id_in_format_string(); } +#if __cpp_lib_format >= 202305L + template + constexpr void + check_dynamic_spec(size_t __id) noexcept; + + constexpr void + check_dynamic_spec_integral(size_t __id) noexcept + { + check_dynamic_spec(__id); + } + + constexpr void + check_dynamic_spec_string(size_t __id) noexcept + { + check_dynamic_spec>(__id); + } + + private: + // Check the Mandates: condition for check_dynamic_spec(n) + template + static consteval bool + __check_dynamic_spec_types() + { + if constexpr (sizeof...(_Ts)) + { + int __counts[] = { + (is_same_v + ...), + (is_same_v<_CharT, _Ts> + ...), + (is_same_v + ...), + (is_same_v + ...), + (is_same_v + ...), + (is_same_v + ...), + (is_same_v + ...), + (is_same_v + ...), + (is_same_v + ...), + (is_same_v + ...), + (is_same_v, _Ts> + ...), + (is_same_v + ...) + }; + int __sum = 0; + for (int __c : __counts) + { + __sum += __c; + if (__c > 1) + __invalid_dynamic_spec("non-unique template argument type"); + } + if (__sum != sizeof...(_Ts)) + __invalid_dynamic_spec("disallowed template argument type"); + } + return true; + } + + // This must not be constexpr. + static void __invalid_dynamic_spec(const char*); + + friend __format::_Scanner<_CharT>; +#endif + + // This constructor should only be used by the implementation. + constexpr explicit + basic_format_parse_context(basic_string_view<_CharT> __fmt, + size_t __num_args) noexcept + : _M_begin(__fmt.begin()), _M_end(__fmt.end()), _M_num_args(__num_args) + { } + private: iterator _M_begin; iterator _M_end; enum _Indexing { _Unknown, _Manual, _Auto }; _Indexing _M_indexing = _Unknown; size_t _M_next_arg_id = 0; - size_t _M_num_args; + size_t _M_num_args = 0; }; /// @cond undocumented @@ -549,6 +616,9 @@ namespace __format __pc.check_arg_id(__v); __val = __v; } +#if __cpp_lib_format >= 202305L + __pc.check_dynamic_spec_integral(__val); +#endif ++__first; // past the '}' } return __first; @@ -3205,6 +3275,10 @@ namespace __format template class _Arg_store; + template + consteval _Arg_t + __to_arg_t_enum() noexcept; + } // namespace __format /// @endcond @@ -3486,6 +3560,10 @@ namespace __format friend decltype(auto) visit_format_arg(_Visitor&& __vis, basic_format_arg<_Ctx>); + template + friend consteval __format::_Arg_t + __format::__to_arg_t_enum() noexcept; + template decltype(auto) _M_visit(_Visitor&& __vis, __format::_Arg_t __type) @@ -3899,7 +3977,11 @@ namespace __format { using iterator = typename basic_format_parse_context<_CharT>::iterator; - basic_format_parse_context<_CharT> _M_pc; + struct _Parse_context : basic_format_parse_context<_CharT> + { + using basic_format_parse_context<_CharT>::basic_format_parse_context; + const _Arg_t* _M_types = nullptr; + } _M_pc; constexpr explicit _Scanner(basic_string_view<_CharT> __str, size_t __nargs = -1) @@ -4059,6 +4141,16 @@ namespace __format } }; + template + consteval _Arg_t + __to_arg_t_enum() noexcept + { + using _Context = __format::__format_context<_CharT>; + using _Fmt_arg = basic_format_arg<_Context>; + using _NormalizedTp = typename _Fmt_arg::template _Normalize<_Tp>; + return _Fmt_arg::template _S_to_enum<_NormalizedTp>(); + } + // Validate a format string for Args. template class _Checking_scanner : public _Scanner<_CharT> @@ -4068,10 +4160,14 @@ namespace __format "std::formatter must be specialized for each type being formatted"); public: - constexpr + consteval _Checking_scanner(basic_string_view<_CharT> __str) : _Scanner<_CharT>(__str, sizeof...(_Args)) - { } + { +#if __cpp_lib_format >= 202305L + this->_M_pc._M_types = _M_types.data(); +#endif + } private: constexpr void @@ -4102,6 +4198,11 @@ namespace __format else __builtin_unreachable(); } + +#if __cpp_lib_format >= 202305L + array<_Arg_t, sizeof...(_Args)> + _M_types{ { __format::__to_arg_t_enum<_CharT, _Args>()... } }; +#endif }; template @@ -4200,6 +4301,33 @@ namespace __format } // namespace __format /// @endcond +#if __cpp_lib_format >= 202305L + template + template + constexpr void + basic_format_parse_context<_CharT>::check_dynamic_spec(size_t __id) noexcept + { + constexpr bool __ok = __check_dynamic_spec_types<_Ts...>(); + + if consteval { + if (__id >= _M_num_args) + __format::__invalid_arg_id_in_format_string(); + if constexpr (sizeof...(_Ts) != 0) + { + using _Parse_context = __format::_Scanner<_CharT>::_Parse_context; + auto __arg = static_cast<_Parse_context*>(this)->_M_types[__id]; + __format::_Arg_t __types[] = { + __format::__to_arg_t_enum<_CharT, _Ts>()... + }; + for (auto __t : __types) + if (__arg == __t) + return; + } + __invalid_dynamic_spec("arg(id) type does not match"); + } + } +#endif + template template requires convertible_to> diff --git a/libstdc++-v3/testsuite/std/format/functions/format.cc b/libstdc++-v3/testsuite/std/format/functions/format.cc index 5152bb0b0d0..3c441d711b3 100644 --- a/libstdc++-v3/testsuite/std/format/functions/format.cc +++ b/libstdc++-v3/testsuite/std/format/functions/format.cc @@ -8,6 +8,8 @@ # error "Feature test macro for std::format is missing in " #elif __cpp_lib_format < 202110L # error "Feature test macro for std::format has wrong value in " +#elif __cplusplus > 202302L && __cpp_lib_format < 202305L +# error "Feature test macro for std::format has wrong value in " #endif #ifndef __cpp_lib_format_uchar @@ -22,6 +24,8 @@ # error "Feature test macro for std::format is missing in " #elif __cpp_lib_format < 202110L # error "Feature test macro for std::format has wrong value in " +#elif __cplusplus > 202302L && __cpp_lib_format < 202305L +# error "Feature test macro for std::format has wrong value in " #endif #ifndef __cpp_lib_format_uchar diff --git a/libstdc++-v3/testsuite/std/format/parse_ctx.cc b/libstdc++-v3/testsuite/std/format/parse_ctx.cc index 3b3201c2a47..b0cef2da41b 100644 --- a/libstdc++-v3/testsuite/std/format/parse_ctx.cc +++ b/libstdc++-v3/testsuite/std/format/parse_ctx.cc @@ -3,6 +3,91 @@ #include #include +static_assert(std::is_constructible_v); +static_assert(std::is_constructible_v); + +#if __cpp_lib_format < 202305 +constexpr bool construct_with_num_args = true; +#else +constexpr bool construct_with_num_args = false; +#endif + +static_assert(std::is_constructible_v + == construct_with_num_args); +static_assert(std::is_constructible_v + == construct_with_num_args); + +static_assert( ! std::is_constructible_v); +static_assert( ! std::is_constructible_v); + +static_assert( ! std::is_convertible_v ); +static_assert( ! std::is_convertible_v ); + +static_assert( ! std::is_default_constructible_v ); +static_assert( ! std::is_copy_constructible_v ); +static_assert( ! std::is_move_constructible_v ); +static_assert( ! std::is_copy_assignable_v ); +static_assert( ! std::is_move_assignable_v ); + +// This concept is satisfied if the next_arg_id() call is a constant expression +template> +concept arg_id_available = requires { + typename std::integral_constant::type; +}; + +void +test_members() +{ + std::string_view s = "spec string"; + + std::format_parse_context pc(s); + + VERIFY( pc.begin() == s.begin() ); + VERIFY( pc.end() == s.end() ); + pc.advance_to(s.begin() + 5); + VERIFY( pc.begin() == s.begin() + 5 ); + + // Runtime calls to these do not check for the correct number of args. + VERIFY( pc.next_arg_id() == 0 ); + VERIFY( pc.next_arg_id() == 1 ); + VERIFY( pc.next_arg_id() == 2 ); + try + { + // Cannot mix manual and automatic indexing. + pc.check_arg_id(0); + VERIFY( false ); + } + catch (const std::format_error&) + { + } + // But they do check during constant evaluation: + VERIFY( ! arg_id_available ); + VERIFY( ! arg_id_available ); + + std::format_parse_context pc2(""); + pc2.check_arg_id(2); + pc2.check_arg_id(1); + pc2.check_arg_id(3); + try + { + // Cannot mix manual and automatic indexing. + (void) pc2.next_arg_id(); + VERIFY( false ); + } + catch (const std::format_error&) + { + } +} + template bool is_std_format_spec_for(std::string_view spec) @@ -357,6 +442,65 @@ test_custom() VERIFY( ! is_std_format_spec_for("") ); } +#if __cpp_lib_format >= 202305 +struct X { }; + +template<> +struct std::formatter +{ + constexpr std::format_parse_context::iterator + parse(std::format_parse_context& pc) + { + std::string_view spec(pc.begin(), pc.end()); + auto p = spec.find('}'); + if (p != std::string_view::npos) + spec = spec.substr(0, p); // truncate to closing brace + if (spec == "int") + { + pc.check_dynamic_spec_integral(pc.next_arg_id()); + integer = true; + } + else if (spec == "str") + { + pc.check_dynamic_spec_string(pc.next_arg_id()); + integer = false; + } + else + throw std::format_error("invalid format-spec"); + return pc.begin() + spec.size(); + } + + std::format_context::iterator + format(X, std::format_context& c) const + { + std::visit_format_arg([this](T) { + if (is_integral_v != this->integer) + throw std::format_error("invalid argument type"); + }, c.arg(1)); + return c.out(); + } +private: + bool integer = false; +}; +#endif + +void +test_dynamic_type_check() +{ +#if __cpp_lib_format >= 202305 + std::format_parse_context pc("{1}.{2}"); + + // None of these calls should do anything at runtime, only during consteval: + pc.check_dynamic_spec<>(0); + pc.check_dynamic_spec(0); + pc.check_dynamic_spec_integral(0); + pc.check_dynamic_spec_string(0); + + (void) std::format("{:int}", X{}, 42L); + (void) std::format("{:str}", X{}, "H2G2"); +#endif +} + int main() { test_char(); @@ -366,4 +510,5 @@ int main() test_string(); test_pointer(); test_custom(); + test_dynamic_type_check(); } diff --git a/libstdc++-v3/testsuite/std/format/parse_ctx_neg.cc b/libstdc++-v3/testsuite/std/format/parse_ctx_neg.cc new file mode 100644 index 00000000000..d6a4366d7d0 --- /dev/null +++ b/libstdc++-v3/testsuite/std/format/parse_ctx_neg.cc @@ -0,0 +1,39 @@ +// { dg-do compile { target c++26 } } + +#include + +void +test_invalid() +{ + std::format_parse_context pc(""); + + // These types are all valid: + pc.check_dynamic_spec(0); + // For some reason, an empty pack of types is valid: + pc.check_dynamic_spec<>(0); + + pc.check_dynamic_spec(0); // { dg-error "here" } + // const void* is allowed, but void* is not + pc.check_dynamic_spec(0); // { dg-error "here" } + // int and long long are allowed, but long is not + pc.check_dynamic_spec(0); // { dg-error "here" } + // char_type is allowed, but other character types are not + pc.check_dynamic_spec(0); // { dg-error "here" } + pc.check_dynamic_spec(0); // { dg-error "here" } + // std::string_view is allowed, but std::string is not + pc.check_dynamic_spec(0); // { dg-error "here" } + pc.check_dynamic_spec(0); // { dg-error "here" } + + std::wformat_parse_context wpc(L""); + wpc.check_dynamic_spec(0); + wpc.check_dynamic_spec(0); // { dg-error "here" } + wpc.check_dynamic_spec(0); // { dg-error "here" } + wpc.check_dynamic_spec(0); // { dg-error "here" } +} + +// Each failure above will point to a call to this non-constexpr function: +// { dg-error "__invalid_dynamic_spec" "" { target *-*-* } 0 } diff --git a/libstdc++-v3/testsuite/std/format/string.cc b/libstdc++-v3/testsuite/std/format/string.cc index ddb3c5625cd..d5f30500139 100644 --- a/libstdc++-v3/testsuite/std/format/string.cc +++ b/libstdc++-v3/testsuite/std/format/string.cc @@ -109,9 +109,16 @@ test_format_spec() VERIFY( ! is_format_string_for("{:#?}", "str") ); VERIFY( ! is_format_string_for("{:#?}", 'c') ); + // The 0 option is not valid for charT and bool. VERIFY( ! is_format_string_for("{:0c}", 'c') ); VERIFY( ! is_format_string_for("{:0s}", true) ); + // Dynamic width arg must be an integer type. + VERIFY( ! is_format_string_for("{:{}d}", 1, 1.5) ); + VERIFY( ! is_format_string_for("{:{}d}", 1, true) ); + VERIFY( ! is_format_string_for("{:{}d}", 1, "str") ); + VERIFY( ! is_format_string_for("{:{}d}", 1, nullptr) ); + // Precision only valid for string and floating-point types. VERIFY( ! is_format_string_for("{:.3d}", 1) ); VERIFY( ! is_format_string_for("{:3.3d}", 1) ); @@ -119,6 +126,12 @@ test_format_spec() VERIFY( ! is_format_string_for("{:3.3s}", 'c') ); VERIFY( ! is_format_string_for("{:3.3p}", nullptr) ); + // Dynamic precision arg must be an integer type. + VERIFY( ! is_format_string_for("{:.{}f}", 1.0, 1.5) ); + VERIFY( ! is_format_string_for("{:.{}f}", 1.0, true) ); + VERIFY( ! is_format_string_for("{:.{}f}", 1.0, "str") ); + VERIFY( ! is_format_string_for("{:.{}f}", 1.0, nullptr) ); + // Invalid presentation types for integers. VERIFY( ! is_format_string_for("{:f}", 1) ); VERIFY( ! is_format_string_for("{:s}", 1) );