diff --git a/Include/cpython/warnings.h b/Include/cpython/warnings.h index 4e3eb88e8ff447..8731fd2e96b716 100644 --- a/Include/cpython/warnings.h +++ b/Include/cpython/warnings.h @@ -18,3 +18,9 @@ PyAPI_FUNC(int) PyErr_WarnExplicitFormat( // DEPRECATED: Use PyErr_WarnEx() instead. #define PyErr_Warn(category, msg) PyErr_WarnEx((category), (msg), 1) + +int _PyErr_WarnExplicitObjectWithContext( + PyObject *category, + PyObject *message, + PyObject *filename, + int lineno); diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index ed4e6265eac438..b57adfadb5af5f 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -1512,6 +1512,24 @@ async def name_4(): pass [[]] + def test_compile_warnings(self): + # See gh-131927 + # Compile warnings originating from the same file and + # line are now only emitted once. + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("default") + compile('1 is 1', '', 'eval') + compile('1 is 1', '', 'eval') + + self.assertEqual(len(caught), 1) + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + compile('1 is 1', '', 'eval') + compile('1 is 1', '', 'eval') + + self.assertEqual(len(caught), 2) + @requires_debug_ranges() class TestSourcePositions(unittest.TestCase): # Ensure that compiled code snippets have correct line and column numbers diff --git a/Python/_warnings.c b/Python/_warnings.c index 4bb83b214ae6cc..5bbd4a9c19f6c9 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -1317,6 +1317,28 @@ PyErr_WarnExplicitObject(PyObject *category, PyObject *message, return 0; } +/* Like PyErr_WarnExplicitObject, but automatically sets up context */ +int +_PyErr_WarnExplicitObjectWithContext(PyObject *category, PyObject *message, + PyObject *filename, int lineno) +{ + PyObject *unused_filename, *module, *registry; + int unused_lineno; + int stack_level = 1; + + if (!setup_context(stack_level, NULL, &unused_filename, &unused_lineno, + &module, ®istry)) { + return -1; + } + + int rc = PyErr_WarnExplicitObject(category, message, filename, lineno, + module, registry); + Py_DECREF(unused_filename); + Py_DECREF(registry); + Py_DECREF(module); + return rc; +} + int PyErr_WarnExplicit(PyObject *category, const char *text, const char *filename_str, int lineno, diff --git a/Python/compile.c b/Python/compile.c index ba780927eff9d6..bb2c2293a38c9a 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -6616,8 +6616,8 @@ compiler_warn(struct compiler *c, location loc, if (msg == NULL) { return ERROR; } - if (PyErr_WarnExplicitObject(PyExc_SyntaxWarning, msg, c->c_filename, - loc.lineno, NULL, NULL) < 0) + if (_PyErr_WarnExplicitObjectWithContext(PyExc_SyntaxWarning, msg, + c->c_filename, loc.lineno) < 0) { if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) { /* Replace the SyntaxWarning exception with a SyntaxError