From 806b89cf63a496933d721fca85d4e5db4d79cc56 Mon Sep 17 00:00:00 2001 From: uditDewan Date: Sun, 26 Jul 2026 15:58:15 -0400 Subject: [PATCH] Print char8_t/char16_t code units that aren't code points as code units PrintTo(char8_t) and PrintTo(char16_t) widened their argument to char32_t and printed it with the U+XXXX notation. That notation names a code point, but char8_t and char16_t hold code units, so it is wrong for every value that does not encode a code point on its own: any non-ASCII char8_t (always part of a multi-unit UTF-8 sequence) and the UTF-16 surrogates D800-DFFF. For example, PrintTo(char8_t(0x80)) printed "U+0080", the code point of a character that value cannot represent. Print those values as code units instead, using the same escape-plus-code form already used for char, unsigned char and wchar_t: char8_t(0x80) now prints as u8'\x80' (128) and char16_t(0xD800) as u'\xD800' (55296). Values that do encode a code point keep the U+XXXX notation. Fixes #4762 --- googletest/include/gtest/gtest-printers.h | 17 +++++++--------- googletest/src/gtest-printers.cc | 22 +++++++++++++++++++++ googletest/test/googletest-printers-test.cc | 18 ++++++++++++++--- 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/googletest/include/gtest/gtest-printers.h b/googletest/include/gtest/gtest-printers.h index a13815733..01ac414bd 100644 --- a/googletest/include/gtest/gtest-printers.h +++ b/googletest/include/gtest/gtest-printers.h @@ -524,17 +524,14 @@ inline void PrintTo(bool x, ::std::ostream* os) { GTEST_API_ void PrintTo(wchar_t wc, ::std::ostream* os); GTEST_API_ void PrintTo(char32_t c, ::std::ostream* os); -inline void PrintTo(char16_t c, ::std::ostream* os) { - // TODO(b/418738869): Incorrect for values not representing valid codepoints. - // Also see https://github.com/google/googletest/issues/4762. - PrintTo(static_cast(c), os); -} + +// Overloads for the UTF-8 and UTF-16 code unit types. A code unit that +// encodes a code point all by itself is printed with the U+XXXX notation; +// one that does not (any non-ASCII char8_t, and the UTF-16 surrogates) is +// printed as a code unit instead, the way wchar_t is. +GTEST_API_ void PrintTo(char16_t c, ::std::ostream* os); #ifdef __cpp_lib_char8_t -inline void PrintTo(char8_t c, ::std::ostream* os) { - // TODO(b/418738869): Incorrect for values not representing valid codepoints. - // Also see https://github.com/google/googletest/issues/4762. - PrintTo(static_cast(c), os); -} +GTEST_API_ void PrintTo(char8_t c, ::std::ostream* os); #endif // gcc/clang __{u,}int128_t diff --git a/googletest/src/gtest-printers.cc b/googletest/src/gtest-printers.cc index 6d1de6d95..d1ba45eab 100644 --- a/googletest/src/gtest-printers.cc +++ b/googletest/src/gtest-printers.cc @@ -285,6 +285,28 @@ void PrintTo(char32_t c, ::std::ostream* os) { << static_cast(c); } +// char8_t and char16_t hold code units, not code points, so the U+XXXX +// notation only applies to the values that encode a code point on their own. +// The rest -- non-ASCII char8_t, which are always part of a multi-unit UTF-8 +// sequence, and the UTF-16 surrogates -- are printed as code units. +void PrintTo(char16_t c, ::std::ostream* os) { + if (c >= 0xD800 && c <= 0xDFFF) { + PrintCharAndCodeTo(c, os); + } else { + PrintTo(static_cast(c), os); + } +} + +#ifdef __cpp_lib_char8_t +void PrintTo(char8_t c, ::std::ostream* os) { + if (c >= 0x80) { + PrintCharAndCodeTo(c, os); + } else { + PrintTo(static_cast(c), os); + } +} +#endif + // gcc/clang __{u,}int128_t #if defined(__SIZEOF_INT128__) void PrintTo(__uint128_t v, ::std::ostream* os) { diff --git a/googletest/test/googletest-printers-test.cc b/googletest/test/googletest-printers-test.cc index 7d7e933e2..042de9c30 100644 --- a/googletest/test/googletest-printers-test.cc +++ b/googletest/test/googletest-printers-test.cc @@ -395,12 +395,24 @@ TEST(PrintCharTest, UnsignedChar) { EXPECT_EQ("'b' (98, 0x62)", Print(static_cast('b'))); } -TEST(PrintCharTest, Char16) { EXPECT_EQ("U+0041", Print(u'A')); } +TEST(PrintCharTest, Char16) { + EXPECT_EQ("U+0041", Print(u'A')); + EXPECT_EQ("U+754C", Print(u'界')); + // Surrogates are not code points, so they are printed as code units. + EXPECT_EQ("u'\\xD800' (55296)", Print(static_cast(0xD800))); + EXPECT_EQ("u'\\xDFFF' (57343)", Print(static_cast(0xDFFF))); +} TEST(PrintCharTest, Char32) { EXPECT_EQ("U+0041", Print(U'A')); } #ifdef __cpp_lib_char8_t -TEST(PrintCharTest, Char8) { EXPECT_EQ("U+0041", Print(u8'A')); } +TEST(PrintCharTest, Char8) { + EXPECT_EQ("U+0041", Print(u8'A')); + // Only ASCII code units encode a code point on their own; the rest are + // printed as code units. + EXPECT_EQ("u8'\\x80' (128)", Print(static_cast(0x80))); + EXPECT_EQ("u8'\\xFF' (255)", Print(static_cast(0xFF))); +} #endif // Tests printing other simple, built-in types. @@ -455,7 +467,7 @@ TEST(PrintBuiltInTypeTest, Integer) { #ifdef __cpp_lib_char8_t EXPECT_EQ("U+0000", Print(std::numeric_limits::min())); // char8_t - EXPECT_EQ("U+00FF", + EXPECT_EQ("u8'\\xFF' (255)", Print(std::numeric_limits::max())); // char8_t #endif EXPECT_EQ("U+0000",