gh-155319: Fix the source line of a warning issued with module_globals (GH-155320)

serhiy-storchaka <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/c92e2fd8f9c8af3ea383dec6222cfcca47a405ff
commit: c92e2fd8f9c8af3ea383dec6222cfcca47a405ff
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-15T09:54:58+03:00
summary:

gh-155319: Fix the source line of a warning issued with module_globals (GH-155320)

warn_explicit() computed the source line from the loader of the module
whose globals were passed as module_globals, but did not pass it to
WarningMessage.  It also raised IndexError if lineno was out of the range
of the module source.

files:
A Misc/NEWS.d/next/Library/2026-08-07-02-20-00.gh-issue-155319.srcline.rst
M Lib/test/test_warnings/__init__.py
M Python/_warnings.c

diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py
index bf1bcf8e6ed5d9a..cde6076dd2f44b7 100644
--- a/Lib/test/test_warnings/__init__.py
+++ b/Lib/test/test_warnings/__init__.py
@@ -1661,6 +1661,37 @@ def test_RuntimeError(self):
                                             _version=version)
 
 
+class WarnExplicitSourceTests(BaseTest):
+    # gh-155319: the source line is taken from the loader of the module
+    # whose globals are passed as module_globals, if the file which name
+    # is passed as filename cannot be read.
+
+    def warn_explicit(self, lineno):
+        with support.captured_stderr() as stderr:
+            with self.module.catch_warnings():
+                self.module.simplefilter("always")
+                self.module.warn_explicit(
+                    'eggs', UserWarning, 'nonexistent', lineno,
+                    module_globals=warning_tests.__dict__)
+        return stderr.getvalue()
+
+    def test_source_line(self):
+        source = warning_tests.__loader__.get_source(warning_tests.__name__)
+        expected = source.splitlines()[0].strip()
+        self.assertEqual(self.warn_explicit(1),
+                         f'nonexistent:1: UserWarning: eggs\n  {expected}\n')
+
+    def test_source_line_out_of_range(self):
+        self.assertEqual(self.warn_explicit(1000),
+                         'nonexistent:1000: UserWarning: eggs\n')
+
+class CWarnExplicitSourceTests(WarnExplicitSourceTests, unittest.TestCase):
+    module = c_warnings
+
+class PyWarnExplicitSourceTests(WarnExplicitSourceTests, unittest.TestCase):
+    module = py_warnings
+
+
 class BootstrapTest(unittest.TestCase):
 
     def test_issue_8766(self):
diff --git a/Misc/NEWS.d/next/Library/2026-08-07-02-20-00.gh-issue-155319.srcline.rst b/Misc/NEWS.d/next/Library/2026-08-07-02-20-00.gh-issue-155319.srcline.rst
new file mode 100644
index 000000000000000..34a7ff83a73c0b8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-07-02-20-00.gh-issue-155319.srcline.rst
@@ -0,0 +1,4 @@
+:func:`warnings.warn_explicit` now displays the source line taken from the
+loader of the module whose globals are passed as *module_globals*.  It also
+no longer raises :exc:`IndexError` if *lineno* is out of the range of the
+module source.
diff --git a/Python/_warnings.c b/Python/_warnings.c
index 4f6de50efa14a8e..992c3e50f2a9f67 100644
--- a/Python/_warnings.c
+++ b/Python/_warnings.c
@@ -747,7 +747,8 @@ call_show_warning(PyThreadState *tstate, PyObject *category,
     }
 
     msg = PyObject_CallFunctionObjArgs(warnmsg_cls, message, category,
-            filename, lineno_obj, Py_None, Py_None,
+            filename, lineno_obj, Py_None,
+            sourceline ? sourceline : Py_None,
             source ? source : Py_None, module,
             NULL);
     Py_DECREF(warnmsg_cls);
@@ -1234,8 +1235,11 @@ get_source_line(PyInterpreterState *interp, PyObject *module_globals, int lineno
     }
 
     /* Get the source line. */
-    source_line = PyList_GetItem(source_list, lineno-1);
-    Py_XINCREF(source_line);
+    if (lineno < 1 || lineno > PyList_GET_SIZE(source_list)) {
+        Py_DECREF(source_list);
+        return NULL;
+    }
+    source_line = Py_NewRef(PyList_GET_ITEM(source_list, lineno-1));
     Py_DECREF(source_list);
     return source_line;
 }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.