Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions crates/vm/src/builtins/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1870,14 +1870,14 @@ impl SliceableSequenceOp for PyStr {
.collect::<AsciiString>()
.into(),
PyKindStr::Utf8(s) => {
let char_len = (range.len() / step) + 1;
let char_len = range.len().div_ceil(step);
let mut out = String::with_capacity(2 * char_len);
out.extend(s.chars().skip(range.start).take(range.len()).step_by(step));
// SAFETY: char_len is accurate
unsafe { Self::new_with_char_len(out, char_len) }
}
PyKindStr::Wtf8(w) => {
let char_len = (range.len() / step) + 1;
let char_len = range.len().div_ceil(step);
let mut out = Wtf8Buf::with_capacity(2 * char_len);
out.extend(
w.code_points()
Expand All @@ -1900,7 +1900,7 @@ impl SliceableSequenceOp for PyStr {
.collect::<AsciiString>()
.into(),
PyKindStr::Utf8(s) => {
let char_len = (range.len() / step) + 1;
let char_len = range.len().div_ceil(step);
// not ascii, so the codepoints have to be at least 2 bytes each
let mut out = String::with_capacity(2 * char_len);
out.extend(
Expand All @@ -1914,7 +1914,7 @@ impl SliceableSequenceOp for PyStr {
unsafe { Self::new_with_char_len(out, char_len) }
}
PyKindStr::Wtf8(w) => {
let char_len = (range.len() / step) + 1;
let char_len = range.len().div_ceil(step);
// not ascii, so the codepoints have to be at least 2 bytes each
let mut out = Wtf8Buf::with_capacity(2 * char_len);
out.extend(
Expand Down
29 changes: 29 additions & 0 deletions extra_tests/snippets/builtin_str_unicode_slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,32 @@ def expect_index_error(s, index):
assert len(hebrew_text[30:10:-3]) == 7
assert hebrew_text[30:10:-1] == "א ,םיִהֹלֱא אָרָּב ,"
assert len(hebrew_text[30:10:-1]) == 20


# A stepped slice whose span is an exact multiple of the step ends on the last
# character it collects rather than one past it, so the character count is the
# span divided by the step and not one more. The subject goes through a
# variable because a constant subscript is folded at compile time and would
# never reach the runtime slice at all.
def stepped(s, step):
return s[::step]


for subject, step, expected in [
("a\u00e9c", 3, "a"),
("가나다라", 2, "가다"),
("가나다라마바", 3, "가라"),
("가나다라", -2, "라나"),
("가나다라마바", -3, "바다"),
("\U0001f600\U0001f601\U0001f602\U0001f603", 2, "\U0001f600\U0001f602"),
]:
sliced = stepped(subject, step)
assert sliced == expected, (subject, step, sliced)
assert len(sliced) == len(expected), (subject, step, len(sliced))
# An overstated count makes the string claim characters its buffer does not
# hold, which reversed() then reads past.
assert list(reversed(sliced)) == list(expected)[::-1]

assert len(stepped(hebrew_text, 2)) == 30
assert len(stepped(hebrew_text, 4)) == 15
assert len(stepped(hebrew_text, -2)) == 30
Loading