Without this, the code would accept `b"eggs" % b"ham"` and `b"eggs" %
bytearray(b"ham")` because it would detect bytes and bytearray as
mappings (which _are_ permitted to have leftover, unprocessed
arguments).
Unlike strings, b'%a' and b'%r' are equivalent, and they match the '%a'
behaviour of strings, not '%r'.
Thanks to @youknowone for improving this implementation.
The previous code assumed that, after parsing was complete, both the
padding and the precision integers would always be positive: it
therefore cast them to usize unconditionally. This meant that when a
negative integer was encountered, it would underflow and attempt to
generate a padded string of around usize::MAX, or a float representation
of that precision: unsurprisingly, these fail to allocate.
For simple format strings (e.g. `%-3s`), this works fine: `-` is
detected as a separate token during parsing, so only the positive
integer (3) is passed through.
However, `%*s` expresses "take the first value as the padding integer,
and the second as the string to be padded" (`"%*s" % (-3, ...)` is
analogous to the previous example). In this codepath, the parsing does
not handle the padding integer, so it can't strip the leading `-`: the
padding integer is negative. A negative precision can also be given in
similar fashion, with `"%.*f" % (-3, ...)`.
N.B. This commit does not cause the code to produce the correct _output_
for negative padding integers or precisions provided this way, it
addresses only the crash.
In CPython, __str__ and __repr__ for OSError show different results.
This PR try to match this behavior but without the part after the
message (in following example is "'123' -> '456'").
```
In : exc.__repr__()
Out: "FileNotFoundError(2, 'No such file or directory')"
In : exc.__str__()
Out: "[Errno 2] No such file or directory: '123' -> '456'"
In : exc.args
Out: (2, 'No such file or directory')
```
Clean implementation of changes in PR #3371 based on feedback.
Copies from [CPython tag `v3.9.7` and adds back custom RustPython changes where needed for:
- `Lib/collections/__init__.py`
- `Lib/test/test_collections.py`
Closes: #3371