Repository: hukkin/tomli

Issue:
Incorrect error message
I ran across an incorrect error message while using `tomli.loads`. See the snippet below

```
import tomli
>>> tomli_str = b'test="a"'
>>> tomli.loads(tomli_str)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "venv\lib\site-packages\tomli\_parser.py", line 74, in loads
    src = __s.replace("\r\n", "\n")
TypeError: a bytes-like object is required, not 'str'
```

Note the actual text of of the type error. It indicates it wants "`a bytes-like object is required, not 'str'`". Except it was passed a bytes like object and consulting the documentation `loads` actually wants a `str` object. So error message text is wrong and in fact opposite of what the function actually wants.

I generated this error using tomli 2.0.1

Discussion:
@hukkin: Thanks for the issue!

The error message is bad because it is not raised directly by tomli but the `bytes.replace` method of the unsupported `bytes` object. Tomli let's it bubble up.

Question to @encukou: Does tomllib (or CPython in general) care about PRs that improve error messages like this?@encukou: Yes, the CPython PRs would be welcome. Ping me so it doesn't get lost in the review queue.

Solution patch:
diff --git a/src/tomli/_parser.py b/src/tomli/_parser.py
index 3cb340e..660c88c 100644
--- a/src/tomli/_parser.py
+++ b/src/tomli/_parser.py
@@ -71,7 +71,12 @@ def loads(__s: str, *, parse_float: ParseFloat = float) -> dict[str, Any]:  # no
 
     # The spec allows converting "\r\n" to "\n", even in string
     # literals. Let's do so to simplify parsing.
-    src = __s.replace("\r\n", "\n")
+    try:
+        src = __s.replace("\r\n", "\n")
+    except (AttributeError, TypeError):
+        raise TypeError(
+            f"Expected str object, not '{type(__s).__qualname__}'"
+        ) from None
     pos = 0
     out = Output(NestedDict(), Flags())
     header: Key = ()


Test patch:
diff --git a/tests/test_error.py b/tests/test_error.py
index 7244626..d2ef59a 100644
--- a/tests/test_error.py
+++ b/tests/test_error.py
@@ -39,6 +39,15 @@ class TestError(unittest.TestCase):
             tomllib.loads("v = '\n'")
         self.assertTrue(" '\\n' " in str(exc_info.exception))
 
+    def test_type_error(self):
+        with self.assertRaises(TypeError) as exc_info:
+            tomllib.loads(b"v = 1")  # type: ignore[arg-type]
+        self.assertEqual(str(exc_info.exception), "Expected str object, not 'bytes'")
+
+        with self.assertRaises(TypeError) as exc_info:
+            tomllib.loads(False)  # type: ignore[arg-type]
+        self.assertEqual(str(exc_info.exception), "Expected str object, not 'bool'")
+
     def test_module_name(self):
         self.assertEqual(tomllib.TOMLDecodeError().__module__, tomllib.__name__)