fix: Swapcase must handle multibyte expansions (#7559)

`swapcase` used `to_ascii_lowercase` and uppercase to swap cases. This
is fine for ASCII, but code points may expand into multiple bytes which
leads to incorrect case swaps for some languages. The fix is to use
`to_lowercase` and `to_uppercase` instead.

Unfortunately, this leads to a realloc in `swapcase` when bytes are
expanded.

Part of #7526.
This commit is contained in:
Joshua Megnauth authored and GitHub committed 2026-04-06 23:29:30 +09:00
1 parent b682ec3bbe
commit 6493f9a31d
2 files changed
+5 -3

No files matched your search

+3 -3
View File
@@ -1045,11 +1045,11 @@ impl PyStr {
let mut swapped_str = Wtf8Buf::with_capacity(self.data.len());
for c_orig in self.as_wtf8().code_points() {
let c = c_orig.to_char_lossy();
// to_uppercase returns an iterator, to_ascii_uppercase returns the char
// to_uppercase returns an iterator because case changes may be multiple bytes
if c.is_lowercase() {
swapped_str.push_char(c.to_ascii_uppercase());
swapped_str.extend(c.to_uppercase());
} else if c.is_uppercase() {
swapped_str.push_char(c.to_ascii_lowercase());
swapped_str.extend(c.to_lowercase());
} else {
swapped_str.push(c_orig);
}
+2
View File
@@ -235,6 +235,8 @@ assert "유니코드".isidentifier()
assert not "😂".isidentifier()
assert not "123".isidentifier()
assert "Σίσυφος".swapcase() == "σΊΣΥΦΟΣ"
# String Formatting
assert "{} {}".format(1, 2) == "1 2"
assert "{0} {1}".format(2, 3) == "2 3"