[libcxx-commits] [libcxx] 8b9d8df - [libc++] Correctly implement __transform_primary on all platforms (#186130)

via libcxx-commits libcxx-commits at lists.llvm.org
Tue Aug 4 03:14:19 PDT 2026


Author: aokblast
Date: 2026-08-04T18:14:14+08:00
New Revision: 8b9d8dfe208f08ffa83321d8b8fbb3a06c98a6df

URL: https://github.com/llvm/llvm-project/commit/8b9d8dfe208f08ffa83321d8b8fbb3a06c98a6df
DIFF: https://github.com/llvm/llvm-project/commit/8b9d8dfe208f08ffa83321d8b8fbb3a06c98a6df.diff

LOG: [libc++] Correctly implement __transform_primary on all platforms (#186130)

Added: 
    

Modified: 
    libcxx/include/__cxx03/regex
    libcxx/include/regex
    libcxx/test/std/re/re.traits/transform_primary.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/include/__cxx03/regex b/libcxx/include/__cxx03/regex
index bbd6eeee19ee9..a0564c91e26c0 100644
--- a/libcxx/include/__cxx03/regex
+++ b/libcxx/include/__cxx03/regex
@@ -1108,6 +1108,26 @@ typename regex_traits<_CharT>::string_type
 regex_traits<_CharT>::__transform_primary(_ForwardIterator __f, _ForwardIterator __l, char) const {
   const string_type __s(__f, __l);
   string_type __d = __col_->transform(__s.data(), __s.data() + __s.size());
+#if defined(__FreeBSD__) || defined(__APPLE__)
+  // FreeBSD's (and Darwin's, which shares the same collation code) strxfrm()
+  // emits the weights one collation level at a time, separating the levels with
+  // a byte ('.') that is chosen to sort below every weight byte.  Primary
+  // equivalence only depends on the first (primary) level, so drop everything
+  // from the first separator onwards.  This keeps characters that share a
+  // primary weight equal (e.g. 'A' and 'A-acute' in cs_CZ) even when the lower
+  // levels expand to a 
diff erent number of weights, which the fixed-size logic
+  // below could not handle.
+  typename string_type::size_type __sep = __d.find('.');
+  if (__sep != string_type::npos)
+    __d.erase(__sep, string_type::npos);
+#elif defined(__GLIBC__)
+  // glibc's strxfrm() likewise writes the whole primary level first, then a
+  // '\1' level separator (and a trailing '\0' after the last level); real
+  // weight bytes are always >= 2.  Keep only the primary level, as above.
+  typename string_type::size_type __sep = __d.find('\1');
+  if (__sep != string_type::npos)
+    __d.erase(__sep, string_type::npos);
+#else
   switch (__d.size()) {
   case 1:
     break;
@@ -1118,6 +1138,7 @@ regex_traits<_CharT>::__transform_primary(_ForwardIterator __f, _ForwardIterator
     __d.clear();
     break;
   }
+#endif
   return __d;
 }
 
@@ -1128,6 +1149,16 @@ typename regex_traits<_CharT>::string_type
 regex_traits<_CharT>::__transform_primary(_ForwardIterator __f, _ForwardIterator __l, wchar_t) const {
   const string_type __s(__f, __l);
   string_type __d = __col_->transform(__s.data(), __s.data() + __s.size());
+#  if defined(__FreeBSD__) || defined(__APPLE__) || defined(__GLIBC__)
+  // As in the char overload above, FreeBSD's, Darwin's and glibc's wcsxfrm() all
+  // separate the collation levels with a weight (the value 1) that sorts below
+  // every real weight.  Keep only the primary level so primary-equivalent
+  // characters compare equal regardless of how many weights their lower levels
+  // expand to.
+  typename string_type::size_type __sep = __d.find(static_cast<_CharT>(1));
+  if (__sep != string_type::npos)
+    __d.erase(__sep, string_type::npos);
+#  else
   switch (__d.size()) {
   case 1:
     break;
@@ -1138,6 +1169,7 @@ regex_traits<_CharT>::__transform_primary(_ForwardIterator __f, _ForwardIterator
     __d.clear();
     break;
   }
+#  endif
   return __d;
 }
 #endif

diff  --git a/libcxx/include/regex b/libcxx/include/regex
index 86ead9143a25f..ce908f651e49b 100644
--- a/libcxx/include/regex
+++ b/libcxx/include/regex
@@ -1130,6 +1130,26 @@ typename regex_traits<_CharT>::string_type
 regex_traits<_CharT>::__transform_primary(_ForwardIterator __f, _ForwardIterator __l, char) const {
   const string_type __s(__f, __l);
   string_type __d = __col_->transform(__s.data(), __s.data() + __s.size());
+#    if defined(__FreeBSD__) || defined(__APPLE__)
+  // FreeBSD's (and Darwin's, which shares the same collation code) strxfrm()
+  // emits the weights one collation level at a time, separating the levels with
+  // a byte ('.') that is chosen to sort below every weight byte.  Primary
+  // equivalence only depends on the first (primary) level, so drop everything
+  // from the first separator onwards.  This keeps characters that share a
+  // primary weight equal (e.g. 'A' and 'A-acute' in cs_CZ) even when the lower
+  // levels expand to a 
diff erent number of weights, which the fixed-size logic
+  // below could not handle.
+  typename string_type::size_type __sep = __d.find('.');
+  if (__sep != string_type::npos)
+    __d.erase(__sep, string_type::npos);
+#    elif defined(__GLIBC__)
+  // glibc's strxfrm() likewise writes the whole primary level first, then a
+  // '\1' level separator (and a trailing '\0' after the last level); real
+  // weight bytes are always >= 2.  Keep only the primary level, as above.
+  typename string_type::size_type __sep = __d.find('\1');
+  if (__sep != string_type::npos)
+    __d.erase(__sep, string_type::npos);
+#    else
   switch (__d.size()) {
   case 1:
     break;
@@ -1140,6 +1160,7 @@ regex_traits<_CharT>::__transform_primary(_ForwardIterator __f, _ForwardIterator
     __d.clear();
     break;
   }
+#    endif
   return __d;
 }
 
@@ -1150,6 +1171,16 @@ typename regex_traits<_CharT>::string_type
 regex_traits<_CharT>::__transform_primary(_ForwardIterator __f, _ForwardIterator __l, wchar_t) const {
   const string_type __s(__f, __l);
   string_type __d = __col_->transform(__s.data(), __s.data() + __s.size());
+#      if defined(__FreeBSD__) || defined(__APPLE__) || defined(__GLIBC__)
+  // As in the char overload above, FreeBSD's, Darwin's and glibc's wcsxfrm() all
+  // separate the collation levels with a weight (the value 1) that sorts below
+  // every real weight.  Keep only the primary level so primary-equivalent
+  // characters compare equal regardless of how many weights their lower levels
+  // expand to.
+  typename string_type::size_type __sep = __d.find(static_cast<_CharT>(1));
+  if (__sep != string_type::npos)
+    __d.erase(__sep, string_type::npos);
+#      else
   switch (__d.size()) {
   case 1:
     break;
@@ -1160,6 +1191,7 @@ regex_traits<_CharT>::__transform_primary(_ForwardIterator __f, _ForwardIterator
     __d.clear();
     break;
   }
+#      endif
   return __d;
 }
 #    endif

diff  --git a/libcxx/test/std/re/re.traits/transform_primary.pass.cpp b/libcxx/test/std/re/re.traits/transform_primary.pass.cpp
index 422e3a591e643..b86804b6eed3a 100644
--- a/libcxx/test/std/re/re.traits/transform_primary.pass.cpp
+++ b/libcxx/test/std/re/re.traits/transform_primary.pass.cpp
@@ -10,7 +10,6 @@
 // XFAIL: netbsd
 
 // XFAIL: LIBCXX-AIX-FIXME
-// XFAIL: LIBCXX-FREEBSD-FIXME
 
 // REQUIRES: locale.cs_CZ.ISO8859-2
 
@@ -34,25 +33,53 @@ int main(int, char**)
     {
         std::regex_traits<char> t;
         const char A[] = "A";
-        const char Aacute[] = "\xC1";
+        const char AA[]      = "AA";
+        const char B[]       = "B";
+        const char BB[]      = "BB";
+        const char Aacute[]  = "\xC1";
+        const char AAacute[] = "\xC1\xC1";
         typedef forward_iterator<const char*> F;
         assert(t.transform_primary(F(A), F(A+1)) !=
                t.transform_primary(F(Aacute), F(Aacute+1)));
+        assert(t.transform_primary(F(B), F(B + 1)) != t.transform_primary(F(Aacute), F(Aacute + 1)));
+        assert(t.transform_primary(F(A), F(A + 1)) != t.transform_primary(F(AAacute), F(AAacute + 2)));
+        assert(t.transform_primary(F(AA), F(AA + 2)) != t.transform_primary(F(AAacute), F(AAacute + 2)));
+        assert(t.transform_primary(F(BB), F(BB + 2)) != t.transform_primary(F(AAacute), F(AAacute + 2)));
+        assert(t.transform_primary(F(AA), F(AA + 2)) != t.transform_primary(F(BB), F(BB + 2)));
         t.imbue(std::locale(LOCALE_cs_CZ_ISO8859_2));
         assert(t.transform_primary(F(A), F(A+1)) ==
                t.transform_primary(F(Aacute), F(Aacute+1)));
+        assert(t.transform_primary(F(B), F(B + 1)) != t.transform_primary(F(Aacute), F(Aacute + 1)));
+        assert(t.transform_primary(F(A), F(A + 1)) != t.transform_primary(F(AAacute), F(AAacute + 2)));
+        assert(t.transform_primary(F(AA), F(AA + 2)) == t.transform_primary(F(AAacute), F(AAacute + 2)));
+        assert(t.transform_primary(F(BB), F(BB + 2)) != t.transform_primary(F(AAacute), F(AAacute + 2)));
+        assert(t.transform_primary(F(AA), F(AA + 2)) != t.transform_primary(F(BB), F(BB + 2)));
     }
 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
     {
         std::regex_traits<wchar_t> t;
         const wchar_t A[] = L"A";
+        const wchar_t AA[]      = L"AA";
+        const wchar_t B[]       = L"B";
+        const wchar_t BB[]      = L"BB";
         const wchar_t Aacute[] = L"\xC1";
+        const wchar_t AAacute[] = L"\xC1\xC1";
         typedef forward_iterator<const wchar_t*> F;
         assert(t.transform_primary(F(A), F(A+1)) !=
                t.transform_primary(F(Aacute), F(Aacute+1)));
+        assert(t.transform_primary(F(B), F(B + 1)) != t.transform_primary(F(Aacute), F(Aacute + 1)));
+        assert(t.transform_primary(F(A), F(A + 1)) != t.transform_primary(F(AAacute), F(AAacute + 2)));
+        assert(t.transform_primary(F(AA), F(AA + 2)) != t.transform_primary(F(AAacute), F(AAacute + 2)));
+        assert(t.transform_primary(F(BB), F(BB + 2)) != t.transform_primary(F(AAacute), F(AAacute + 2)));
+        assert(t.transform_primary(F(AA), F(AA + 2)) != t.transform_primary(F(BB), F(BB + 2)));
         t.imbue(std::locale(LOCALE_cs_CZ_ISO8859_2));
         assert(t.transform_primary(F(A), F(A+1)) ==
                t.transform_primary(F(Aacute), F(Aacute+1)));
+        assert(t.transform_primary(F(B), F(B + 1)) != t.transform_primary(F(Aacute), F(Aacute + 1)));
+        assert(t.transform_primary(F(A), F(A + 1)) != t.transform_primary(F(AAacute), F(AAacute + 2)));
+        assert(t.transform_primary(F(AA), F(AA + 2)) == t.transform_primary(F(AAacute), F(AAacute + 2)));
+        assert(t.transform_primary(F(BB), F(BB + 2)) != t.transform_primary(F(AAacute), F(AAacute + 2)));
+        assert(t.transform_primary(F(AA), F(AA + 2)) != t.transform_primary(F(BB), F(BB + 2)));
     }
 #endif
 


        


More information about the libcxx-commits mailing list