Skip to content

Commit 5c30078

Browse files
committed
[3.13] pythongh-131927: Prevent emitting optimizer warnings twice in the REPL (pythonGH-131993)
(cherry picked from commit 3d08c8a) Co-authored-by: Tomas R. <[email protected]>
1 parent 5be7505 commit 5c30078

File tree

5 files changed

+118
-0
lines changed

5 files changed

+118
-0
lines changed

Include/cpython/warnings.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,9 @@ PyAPI_FUNC(int) PyErr_WarnExplicitFormat(
1818

1919
// DEPRECATED: Use PyErr_WarnEx() instead.
2020
#define PyErr_Warn(category, msg) PyErr_WarnEx((category), (msg), 1)
21+
22+
int _PyErr_WarnExplicitObjectWithContext(
23+
PyObject *category,
24+
PyObject *message,
25+
PyObject *filename,
26+
int lineno);

Lib/test/test_compile.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,6 +1512,24 @@ async def name_4():
15121512
pass
15131513
[[]]
15141514

1515+
def test_compile_warnings(self):
1516+
# See gh-131927
1517+
# Compile warnings originating from the same file and
1518+
# line are now only emitted once.
1519+
with warnings.catch_warnings(record=True) as caught:
1520+
warnings.simplefilter("default")
1521+
compile('1 is 1', '<stdin>', 'eval')
1522+
compile('1 is 1', '<stdin>', 'eval')
1523+
1524+
self.assertEqual(len(caught), 1)
1525+
1526+
with warnings.catch_warnings(record=True) as caught:
1527+
warnings.simplefilter("always")
1528+
compile('1 is 1', '<stdin>', 'eval')
1529+
compile('1 is 1', '<stdin>', 'eval')
1530+
1531+
self.assertEqual(len(caught), 2)
1532+
15151533
@requires_debug_ranges()
15161534
class TestSourcePositions(unittest.TestCase):
15171535
# Ensure that compiled code snippets have correct line and column numbers

Lib/test/test_pyrepl/test_interact.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import contextlib
22
import io
33
import unittest
4+
import warnings
45
from unittest.mock import patch
56
from textwrap import dedent
67

@@ -273,3 +274,28 @@ def test_incomplete_statement(self):
273274
code = "if foo:"
274275
console = InteractiveColoredConsole(namespace, filename="<stdin>")
275276
self.assertTrue(_more_lines(console, code))
277+
278+
279+
class TestWarnings(unittest.TestCase):
280+
def test_pep_765_warning(self):
281+
"""
282+
Test that a SyntaxWarning emitted from the
283+
AST optimizer is only shown once in the REPL.
284+
"""
285+
# gh-131927
286+
console = InteractiveColoredConsole()
287+
code = dedent("""\
288+
def f():
289+
try:
290+
return 1
291+
finally:
292+
return 2
293+
""")
294+
295+
with warnings.catch_warnings(record=True) as caught:
296+
warnings.simplefilter("default")
297+
console.runsource(code)
298+
299+
count = sum("'return' in a 'finally' block" in str(w.message)
300+
for w in caught)
301+
self.assertEqual(count, 1)

Python/_warnings.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,28 @@ PyErr_WarnExplicitObject(PyObject *category, PyObject *message,
13171317
return 0;
13181318
}
13191319

1320+
/* Like PyErr_WarnExplicitObject, but automatically sets up context */
1321+
int
1322+
_PyErr_WarnExplicitObjectWithContext(PyObject *category, PyObject *message,
1323+
PyObject *filename, int lineno)
1324+
{
1325+
PyObject *unused_filename, *module, *registry;
1326+
int unused_lineno;
1327+
int stack_level = 1;
1328+
1329+
if (!setup_context(stack_level, NULL, &unused_filename, &unused_lineno,
1330+
&module, &registry)) {
1331+
return -1;
1332+
}
1333+
1334+
int rc = PyErr_WarnExplicitObject(category, message, filename, lineno,
1335+
module, registry);
1336+
Py_DECREF(unused_filename);
1337+
Py_DECREF(registry);
1338+
Py_DECREF(module);
1339+
return rc;
1340+
}
1341+
13201342
int
13211343
PyErr_WarnExplicit(PyObject *category, const char *text,
13221344
const char *filename_str, int lineno,

Python/errors.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1868,6 +1868,52 @@ PyErr_SyntaxLocationEx(const char *filename, int lineno, int col_offset)
18681868
Py_XDECREF(fileobj);
18691869
}
18701870

1871+
/* Raises a SyntaxError.
1872+
* If something goes wrong, a different exception may be raised.
1873+
*/
1874+
void
1875+
_PyErr_RaiseSyntaxError(PyObject *msg, PyObject *filename, int lineno, int col_offset,
1876+
int end_lineno, int end_col_offset)
1877+
{
1878+
PyObject *text = PyErr_ProgramTextObject(filename, lineno);
1879+
if (text == NULL) {
1880+
text = Py_NewRef(Py_None);
1881+
}
1882+
PyObject *args = Py_BuildValue("O(OiiOii)", msg, filename,
1883+
lineno, col_offset, text,
1884+
end_lineno, end_col_offset);
1885+
if (args == NULL) {
1886+
goto exit;
1887+
}
1888+
PyErr_SetObject(PyExc_SyntaxError, args);
1889+
exit:
1890+
Py_DECREF(text);
1891+
Py_XDECREF(args);
1892+
}
1893+
1894+
/* Emits a SyntaxWarning and returns 0 on success.
1895+
If a SyntaxWarning is raised as error, replaces it with a SyntaxError
1896+
and returns -1.
1897+
*/
1898+
int
1899+
_PyErr_EmitSyntaxWarning(PyObject *msg, PyObject *filename, int lineno, int col_offset,
1900+
int end_lineno, int end_col_offset)
1901+
{
1902+
if (_PyErr_WarnExplicitObjectWithContext(PyExc_SyntaxWarning, msg,
1903+
filename, lineno) < 0)
1904+
{
1905+
if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) {
1906+
/* Replace the SyntaxWarning exception with a SyntaxError
1907+
to get a more accurate error report */
1908+
PyErr_Clear();
1909+
_PyErr_RaiseSyntaxError(msg, filename, lineno, col_offset,
1910+
end_lineno, end_col_offset);
1911+
}
1912+
return -1;
1913+
}
1914+
return 0;
1915+
}
1916+
18711917
/* Attempt to load the line of text that the exception refers to. If it
18721918
fails, it will return NULL but will not set an exception.
18731919

0 commit comments

Comments
 (0)