Repository: python/cpython
Pull request: gh-115323: Add meaningful error message for using bytearray.extend with str
<!--
Thanks for your contribution!
Please read this comment in its entirety. It's quite important.

# Pull Request title

It should be in the following format:

```
gh-NNNNN: Summary of the changes made
```

Where: gh-NNNNN refers to the GitHub issue number.

Most PRs will require an issue number. Trivial changes, like fixing a typo, do not need an issue.

# Backport Pull Request title

If this is a backport PR (PR made against branches other than `main`),
please ensure that the PR title is in the following format:

```
[X.Y] <title from the original PR> (GH-NNNN)
```

Where: [X.Y] is the branch name, e.g. [3.6].

GH-NNNN refers to the PR number from `main`.

-->

Fixes #115323

- Check if the iterable is an instance of `str`
- Raise a TypeError when the iterable is an instance of `str`, with a more meaningful error message that says the string should be encoded

<!-- gh-issue-number: gh-115323 -->
* Issue: gh-115323
<!-- /gh-issue-number -->


Problem statement:
bytearray.extend: Misleading error message
### Bug description:

```python
b=bytearray(b"abc")
b.extend('def')
```
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object cannot be interpreted as an integer```
```
… except that `bytes.extend` doesn't even accept integers as arguments; the error refers to the first element of the string, which happens also to be a string in Python. Meh.

Could CPython please complain about requiring the string to be encoded instead?

### CPython versions tested on:

main branch

### Operating systems tested on:

Linux

<!-- gh-linked-prs -->
### Linked PRs
* gh-115332
* gh-115894
* gh-115895
<!-- /gh-linked-prs -->

Hints from issue discussion:

Commit message: Add meaningful error message for using bytearray.extend with str

Patch to review:
diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py
index a3804a945f2e3a..5e1fb342cba7d9 100644
--- a/Lib/test/test_bytes.py
+++ b/Lib/test/test_bytes.py
@@ -1599,6 +1599,8 @@ def test_extend(self):
         a = bytearray(b'')
         a.extend([Indexable(ord('a'))])
         self.assertEqual(a, b'a')
+        a = bytearray(b'abc')
+        self.assertRaises(TypeError, a.extend, 'def', msg="'str' object should be encoded")
 
     def test_remove(self):
         b = bytearray(b'hello')
diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c
index acc59b926448ca..eed31cbea34d65 100644
--- a/Objects/bytearrayobject.c
+++ b/Objects/bytearrayobject.c
@@ -1695,6 +1695,13 @@ bytearray_extend(PyByteArrayObject *self, PyObject *iterable_of_ints)
     int value;
     char *buf;
 
+    if (PyUnicode_Check(iterable_of_ints)) {
+        PyErr_Format(PyExc_TypeError,
+                     "'%.100s' object should be encoded",
+                     Py_TYPE(iterable_of_ints)->tp_name);
+        return NULL;
+    }
+
     /* bytearray_setslice code only accepts something supporting PEP 3118. */
     if (PyObject_CheckBuffer(iterable_of_ints)) {
         if (bytearray_setslice(self, Py_SIZE(self), Py_SIZE(self), iterable_of_ints) == -1)


Code review comments:
diff_hunk: @@ -1695,6 +1695,13 @@ bytearray_extend(PyByteArrayObject *self, PyObject *iterable_of_ints)
     int value;
     char *buf;
 
+    if (PyUnicode_Check(iterable_of_ints)) {
path: Objects/bytearrayobject.c
text: @user1:
This check is in the fast path and `PyUnicode_Check` checks for subtypes. Does it slow down the normal case?

@author:
Would you prefer that I move it into the `_getbytevalue` guard clause in the while loop further down? This way, the check is only done if something actually goes wrong.

@user2:
Moving the test makes sense.

@user3:
done