Fix bytes.isspace (#5655)

* Fix implementation of bytes.isspace to match the implementation in CPython.

* Fix implementation of bytes.istitle so that test_bigmem now passes.
Also added some extra_test to capture this.

* Fix some clippy issues

Signed-off-by: Hanif Ariffin <hanif.ariffin.4326@gmail.com>

* bytes.istitle returns false when the bytes is empty.

* Fix some edges of istitle with non-alphanumeric characters

* Disable a line because python 3.12 errors on bad escape sequence

* Match CPython's istitle a little closer

* Fix clippy warning

* Rewrite bytes.istitle along _Py_bytes_istitle

The lookahead-based check accepted b"Not--a Titlecase String", which
test_bytes covers. Track whether the previous byte was cased instead.

isspace compares bytes directly rather than converting to char.

Restore the b"omkmok\Xaa" assert: 3.14 reports an invalid escape
sequence as a SyntaxWarning, not an error.

Assisted-by: Claude

---------

Signed-off-by: Hanif Ariffin <hanif.ariffin.4326@gmail.com>
Co-authored-by: Jeong YunWon <jeong@youknowone.org>
This commit is contained in:
Hanif AriffinandJeong YunWon authored and GitHub committed 2026-08-03 17:52:42 +09:00
1 parent 74254421e4
commit 8e920098b2
3 files changed
+60 -41

No files matched your search

-16
View File
@@ -789,14 +789,6 @@ class BytesTest(unittest.TestCase, BaseStrTest):
def test_swapcase(self, size):
self._test_swapcase(size)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_isspace(self):
return super().test_isspace()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_istitle(self):
return super().test_istitle()
class BytearrayTest(unittest.TestCase, BaseStrTest):
@@ -823,14 +815,6 @@ class BytearrayTest(unittest.TestCase, BaseStrTest):
test_hash = None
test_split_large = None
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_isspace(self):
return super().test_isspace()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_istitle(self):
return super().test_istitle()
class TupleTest(unittest.TestCase):
# Tuples have a small, fixed-sized head and an array of pointers to
+21 -25
View File
@@ -397,44 +397,40 @@ impl PyBytesInner {
self.elements.py_isupper()
}
// _Py_bytes_isspace
pub fn isspace(&self) -> bool {
// is_ascii_whitespace excludes vertical tab, while Py_ISSPACE accepts it
!self.elements.is_empty()
&& self
.elements
.iter()
.all(|x| char::from(*x).is_ascii_whitespace())
.all(|x| x.is_ascii_whitespace() || *x == b'\x0b')
}
// _Py_bytes_istitle
pub fn istitle(&self) -> bool {
if self.elements.is_empty() {
return false;
}
let mut cased = false;
let mut previous_is_cased = false;
let mut iter = self.elements.iter().peekable();
let mut prev_cased = false;
while let Some(c) = iter.next() {
let current = char::from(*c);
let next = if let Some(k) = iter.peek() {
char::from(**k)
} else if current.is_uppercase() {
return !prev_cased;
for byte in &self.elements {
if byte.is_ascii_uppercase() {
if previous_is_cased {
return false;
}
previous_is_cased = true;
cased = true;
} else if byte.is_ascii_lowercase() {
if !previous_is_cased {
return false;
}
previous_is_cased = true;
cased = true;
} else {
return prev_cased;
};
let is_cased = current.to_uppercase().next().unwrap() != current
|| current.to_lowercase().next().unwrap() != current;
if (is_cased && next.is_uppercase() && !prev_cased)
|| (!is_cased && next.is_lowercase())
{
return false;
previous_is_cased = false;
}
prev_cased = is_cased;
}
true
cased
}
pub fn lower(&self) -> Vec<u8> {
+39
View File
@@ -708,3 +708,42 @@ b = B1.fromhex("a0a1a2")
assert b.foo == "bar"
skip_if_unsupported(3, 11, test__bytes__)
assert " \f\n\r\t\v".encode("utf-8").isspace()
assert " \f\n\r\t\v".encode("latin-1").isspace()
# bytes.istitle tests
s = b"Aa6A"
assert s.istitle(), f"{s}"
s = b"Aa6aA"
assert not s.istitle(), f"{s}"
s = b"Python Is Fun"
assert s.istitle(), f"{s}"
s = b"Python is fun"
assert not s.istitle(), f"{s}"
s = b"PYTHON IS FUN"
assert not s.istitle(), f"{s}"
s = b"Python 3.9 Is Awesome!"
assert s.istitle(), f"{s}"
s = b""
assert not s.istitle(), f"{s}"
s = b"Hello Is Amazing"
assert s.istitle(), f"{s}"
s = b"Not--a Titlecase String"
assert not s.istitle(), f"{s}"
s = b"123A"
assert s.istitle(), f"{s}"
s = b"123a"
assert not s.istitle(), f"{s}"
s = b"123A\ta"
assert not s.istitle(), f"{s}"
SUBSTR = b"123456"
s = b"".join([b"A", b"a" * 64, SUBSTR])
assert s.istitle(), f"{s}"
s += b"A"
assert s.istitle(), f"{s}"
s += b"aA"
assert not s.istitle(), f"{s}"
assert "123A".istitle(), f"{s}"
assert not "123a".istitle(), f"{s}"
assert not "123A\ta".istitle(), f"{s}"