test: assert line numbers of evaluated source (#6169)

* test: assert line numbers of evaluated source

#6089 dropped the coding cookie prepended to py::eval/py::exec source,
which had shifted reported SyntaxError and traceback line numbers by one,
and noted that no test asserted on them. Add one: errors on line 2 of the
evaluated source must report line 2. With pybind11's own test target the
new test reports (3, 3) against the parent of #6089 and (2, 2) with the fix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* style: pre-commit fixes

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
diff --git a/tests/test_eval.cpp b/tests/test_eval.cpp
index cd2903f..6fdf7a8 100644
--- a/tests/test_eval.cpp
+++ b/tests/test_eval.cpp
@@ -87,6 +87,31 @@
         return false;
     });
 
+    // Line numbers reported from evaluated source must match the source as given;
+    // before #6089 a prepended coding cookie shifted them by one.
+    m.def("test_eval_line_numbers", []() {
+        int syntax_lineno = -1;
+        try {
+            py::exec("x = 1\nx = = 2\n");
+            throw std::runtime_error("py::exec did not raise SyntaxError");
+        } catch (py::error_already_set &e) {
+            syntax_lineno = e.value().attr("lineno").cast<int>();
+        }
+        int traceback_lineno = -1;
+        try {
+            py::exec("x = 1\nraise RuntimeError('line two')\n");
+            throw std::runtime_error("py::exec did not raise RuntimeError");
+        } catch (py::error_already_set &e) {
+            py::object tb = e.trace();
+            for (py::object next = tb.attr("tb_next"); !next.is_none();
+                 next = tb.attr("tb_next")) {
+                tb = std::move(next);
+            }
+            traceback_lineno = tb.attr("tb_lineno").cast<int>();
+        }
+        return py::make_tuple(syntax_lineno, traceback_lineno);
+    });
+
     // test_eval_empty_globals
     m.def("eval_empty_globals", [](py::object global) {
         if (global.is_none()) {
diff --git a/tests/test_eval.py b/tests/test_eval.py
index 8ac1907..db6cec8 100644
--- a/tests/test_eval.py
+++ b/tests/test_eval.py
@@ -27,6 +27,10 @@
     assert m.test_eval_file_failure()
 
 
+def test_eval_line_numbers():
+    assert m.test_eval_line_numbers() == (2, 2)
+
+
 def test_eval_empty_globals():
     assert "__builtins__" in m.eval_empty_globals(None)