diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index bcbea1b29320275..7fbb1c99823770c 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -1536,6 +1536,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 4bb83b214ae6cc7..fcd2c774d0a8f8f 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -595,7 +595,9 @@ call_show_warning(PyThreadState *tstate, PyObject *category, } msg = PyObject_CallFunctionObjArgs(warnmsg_cls, message, category, - filename, lineno_obj, Py_None, Py_None, source, + filename, lineno_obj, Py_None, + sourceline ? sourceline : Py_None, + source, NULL); Py_DECREF(warnmsg_cls); if (msg == NULL) @@ -1100,8 +1102,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; }