Skip to content

[libc++][chrono] Fix streaming for unsigned durations. #97889

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

mordante
Copy link
Member

@mordante mordante commented Jul 6, 2024

This fixes formatting for durations using the unsigned types unsigned short, unsigned, unsigned long, and unsigned long long. It does not allow the unsigned char type. Since the formatting function uses ostream::operator<< this type probably does not do what it should do.

Note that based on the standard all arithmetic types are allowed, including bool, char, wchar_t. These types are not implemented either. Allowing them seems like a defect in the Standard.

No effort is done to support user-defined types; the wording in the Standard is unclear regarding the constrains for these types.

LWG 4118 discusses this issue further.

Fixes #96820

@mordante mordante requested a review from a team as a code owner July 6, 2024 11:17
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jul 6, 2024
@llvmbot
Copy link
Member

llvmbot commented Jul 6, 2024

@llvm/pr-subscribers-libcxx

Author: Mark de Wever (mordante)

Changes

This fixes formatting for durations using the unsigned types unsigned short, unsigned, unsigned long, and unsigned long long. It does not allow the unsigned char type. Since the formatting function uses ostream::operator<< this type probably does not what it should do.

Note that based on the standard all artithemetic types are allowed, including bool, char, wchar_t. These types are not implemented either. Allowing them seems like a defect in the Standard.

No effort is done to support user-defined types; the wording in the Standard is unclear regarding the constrains for these types.

Fixes #96820


Full diff: https://github.com/llvm/llvm-project/pull/97889.diff

3 Files Affected:

  • (modified) libcxx/include/__chrono/formatter.h (+9-3)
  • (modified) libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp (+25)
  • (modified) libcxx/test/std/time/time.syn/formatter.duration.pass.cpp (+12)
diff --git a/libcxx/include/__chrono/formatter.h b/libcxx/include/__chrono/formatter.h
index e9b81c3de8a700..22694eb3bd083c 100644
--- a/libcxx/include/__chrono/formatter.h
+++ b/libcxx/include/__chrono/formatter.h
@@ -46,6 +46,7 @@
 #include <__memory/addressof.h>
 #include <cmath>
 #include <ctime>
+#include <limits>
 #include <sstream>
 #include <string_view>
 
@@ -589,9 +590,14 @@ __format_chrono(const _Tp& __value,
     __sstr << __value;
   else {
     if constexpr (chrono::__is_duration<_Tp>::value) {
-      if (__value < __value.zero())
-        __sstr << _CharT('-');
-      __formatter::__format_chrono_using_chrono_specs(__sstr, chrono::abs(__value), __chrono_specs);
+      if constexpr (numeric_limits<typename _Tp::rep>::is_signed) {
+        if (__value < __value.zero()) {
+          __sstr << _CharT('-');
+          __formatter::__format_chrono_using_chrono_specs(__sstr, -__value, __chrono_specs);
+        } else
+          __formatter::__format_chrono_using_chrono_specs(__sstr, __value, __chrono_specs);
+      } else
+        __formatter::__format_chrono_using_chrono_specs(__sstr, __value, __chrono_specs);
       // TODO FMT When keeping the precision it will truncate the string.
       // Note that the behaviour what the precision does isn't specified.
       __specs.__precision_ = -1;
diff --git a/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp b/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp
index e5d11ab4672bdf..aecb96b58719e2 100644
--- a/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp
+++ b/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp
@@ -216,10 +216,35 @@ static void test_units() {
   assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::ratio<11, 9>>(0)) == SV("0[11/9]s"));
 }
 
+template <class CharT>
+static void test_unsigned_types() {
+  // Reported in https://github.com/llvm/llvm-project/issues/96820
+  using namespace std::literals::chrono_literals;
+
+  // C locale
+  assert(stream_c_locale<CharT>(std::chrono::duration<unsigned short, std::atto>(0)) == SV("0as"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<unsigned, std::femto>(0)) == SV("0fs"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<unsigned long, std::pico>(0)) == SV("0ps"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<unsigned long long, std::nano>(0)) == SV("0ns"));
+
+  // fr_FR locale
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<unsigned short, std::atto>(0)) == SV("0as"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<unsigned, std::femto>(0)) == SV("0fs"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<unsigned long, std::pico>(0)) == SV("0ps"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<unsigned long long, std::nano>(0)) == SV("0ns"));
+
+  // ja_JP locale
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<unsigned short, std::atto>(0)) == SV("0as"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<unsigned, std::femto>(0)) == SV("0fs"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<unsigned long, std::pico>(0)) == SV("0ps"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<unsigned long long, std::nano>(0)) == SV("0ns"));
+}
+
 template <class CharT>
 static void test() {
   test_values<CharT>();
   test_units<CharT>();
+  test_unsigned_types<CharT>();
 }
 
 int main(int, char**) {
diff --git a/libcxx/test/std/time/time.syn/formatter.duration.pass.cpp b/libcxx/test/std/time/time.syn/formatter.duration.pass.cpp
index dbf373a19ba806..b503d8ecc2817c 100644
--- a/libcxx/test/std/time/time.syn/formatter.duration.pass.cpp
+++ b/libcxx/test/std/time/time.syn/formatter.duration.pass.cpp
@@ -1157,12 +1157,24 @@ static void test_pr62082() {
   check(SV("00.111111"), SV("{:%S}"), std::chrono::duration<long double, std::ratio<1, 9>>{1});
 }
 
+template <class CharT>
+static void test_unsigned_duration() {
+  // Reported in https://github.com/llvm/llvm-project/issues/96820
+  using namespace std::literals::chrono_literals;
+
+  check(SV("1as"), SV("{}"), std::chrono::duration<unsigned short, std::atto>(500));
+  check(SV("1fs"), SV("{}"), std::chrono::duration<unsigned, std::femto>(1));
+  check(SV("1ps"), SV("{}"), std::chrono::duration<unsigned long, std::pico>(1));
+  check(SV("1ns"), SV("{}"), std::chrono::duration<unsigned long long, std::nano>(1));
+}
+
 template <class CharT>
 static void test() {
   using namespace std::literals::chrono_literals;
 
   test_no_chrono_specs<CharT>();
   test_valid_values<CharT>();
+  test_unsigned_duration<CharT>();
   test_pr62082<CharT>();
 
   check_invalid_types<CharT>(

@mordante mordante force-pushed the review/chrono_formatting_unsigned_duration branch from b0a3801 to ba3864a Compare July 6, 2024 12:49
@ldionne ldionne added this to the LLVM 19.X Release milestone Jul 9, 2024
if (__value < __value.zero())
__sstr << _CharT('-');
__formatter::__format_chrono_using_chrono_specs(__sstr, chrono::abs(__value), __chrono_specs);
if constexpr (numeric_limits<typename _Tp::rep>::is_signed) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use std::is_signed_v<typename _Tp::rep> instead. It allows you to include only <__type_traits/is_signed.h>.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually there is a difference between the two. Users may specialize numeric_limits, but specializing is_signed is not allowed. Durations can use a user defined arithmetic type. So using numeric_limits seems to be the better choice. I've added comment in the code.

@mordante mordante force-pushed the review/chrono_formatting_unsigned_duration branch from ba3864a to a782925 Compare July 10, 2024 05:55
This fixes formatting for durations using the unsigned types
unsigned short, unsigned, unsigned long, and unsigned long long. It does
not allow the unsigned char type. Since the formatting function uses
ostream::operator<< this type probably does not what it should do.

Note that based on the standard all artithemetic types are allowed,
including bool, char, wchar_t. These types are not implemented either.
Allowing them seems like a defect in the Standard.

No effort is done to support user-defined types; the wording in the
Standard is unclear regarding the constrains for these types.

Fixes llvm#96820
@mordante mordante force-pushed the review/chrono_formatting_unsigned_duration branch from a782925 to 03c4f8c Compare July 10, 2024 05:58
Copy link

github-actions bot commented Jul 10, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@mordante mordante merged commit 6c97ad4 into llvm:main Jul 10, 2024
55 checks passed
@mordante mordante deleted the review/chrono_formatting_unsigned_duration branch July 10, 2024 18:13
aaryanshukla pushed a commit to aaryanshukla/llvm-project that referenced this pull request Jul 14, 2024
This fixes formatting for durations using the unsigned types unsigned
short, unsigned, unsigned long, and unsigned long long. It does not
allow the unsigned char type. Since the formatting function uses
ostream::operator<< this type probably does not do what it should do.

Note that based on the standard all arithmetic types are allowed,
including bool, char, wchar_t. These types are not implemented either.
Allowing them seems like a defect in the Standard.

No effort is done to support user-defined types; the wording in the
Standard is unclear regarding the constrains for these types.

[LWG 4118](https://cplusplus.github.io/LWG/issue4118) discusses this
issue further.

Fixes llvm#96820
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cannot format a duration with unsigned (or user-defined) representation
3 participants