mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
`do_stepped_slice` and `do_stepped_slice_reverse` took the character count of
a non-ASCII result as `(range.len() / step) + 1`, which overshoots by one
whenever the span is an exact multiple of the step: `"aéc"[::3]` is one
character and reported two, `"가나다라"[::2]` two and reported three. ASCII
subjects were unaffected, since that arm collects into an `AsciiString` whose
length comes from the data.
The count is stored as the string's character length, so the result then
claimed a character its buffer does not hold, and `reversed()` on it indexed
past the end and panicked:
s = "".join(["a", "é", "c"])
list(reversed(s[::3])) # index out of bounds: the len is 1 but the index is 1
Use `div_ceil`, which is the number of elements the underlying range yields.
A differential over 44331 subscript and slice shapes -- every combination of
start, stop and step over strings straddling the ASCII split, the surrogate
range and the astral plane -- now matches CPython 3.14 exactly, where it
differed on 3100 lines before.
Assisted-by: Claude
91 lines
3.0 KiB
Python
91 lines
3.0 KiB
Python
def test_slice_bounds(s):
|
|
# End out of range
|
|
assert s[0:100] == s
|
|
assert s[0:-100] == ""
|
|
# Start out of range
|
|
assert s[100:1] == ""
|
|
# Out of range both sides
|
|
# This is the behaviour in cpython
|
|
# assert s[-100:100] == s
|
|
|
|
|
|
def expect_index_error(s, index):
|
|
try:
|
|
s[index]
|
|
except IndexError:
|
|
pass
|
|
else:
|
|
assert False
|
|
|
|
|
|
unicode_str = "∀∂"
|
|
assert unicode_str[0] == "∀"
|
|
assert unicode_str[1] == "∂"
|
|
assert unicode_str[-1] == "∂"
|
|
|
|
test_slice_bounds(unicode_str)
|
|
expect_index_error(unicode_str, 100)
|
|
expect_index_error(unicode_str, -100)
|
|
|
|
ascii_str = "hello world"
|
|
test_slice_bounds(ascii_str)
|
|
assert ascii_str[0] == "h"
|
|
assert ascii_str[1] == "e"
|
|
assert ascii_str[-1] == "d"
|
|
|
|
# test unicode indexing, made more tricky by hebrew being a right-to-left language
|
|
hebrew_text = "בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ"
|
|
assert len(hebrew_text) == 60
|
|
assert len(hebrew_text[:]) == 60
|
|
assert hebrew_text[0] == "ב"
|
|
assert hebrew_text[1] == "ְ"
|
|
assert hebrew_text[2] == "ּ"
|
|
assert hebrew_text[3] == "ר"
|
|
assert hebrew_text[4] == "ֵ"
|
|
assert hebrew_text[5] == "א"
|
|
assert hebrew_text[6] == "ש"
|
|
assert hebrew_text[5:10] == "אשִׁי"
|
|
assert len(hebrew_text[5:10]) == 5
|
|
assert hebrew_text[-20:50] == "מַיִם, וְא"
|
|
assert len(hebrew_text[-20:50]) == 10
|
|
assert hebrew_text[:-30:1] == "בְּרֵאשִׁית, בָּרָא אֱלֹהִים, "
|
|
assert len(hebrew_text[:-30:1]) == 30
|
|
assert hebrew_text[10:-30] == "ת, בָּרָא אֱלֹהִים, "
|
|
assert len(hebrew_text[10:-30]) == 20
|
|
assert hebrew_text[10:30:3] == "תבר לִ,"
|
|
assert len(hebrew_text[10:30:3]) == 7
|
|
assert hebrew_text[10:30:-3] == ""
|
|
assert hebrew_text[30:10:-3] == "אםהֱאּ "
|
|
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
|