Skip to content

Commit c04d874

Browse files
gh-139640: Fix swallowing syntax warnings in different modules
Revert GH-131993. Fix swallowing some syntax warnings in different modules if they accidentally have the same message and are emitted from the same line. ast.parse() no longer emits syntax warnings for return/break/continue in finally (see PEP-765) -- they are only emitted during compilation.
1 parent 12805ef commit c04d874

File tree

9 files changed

+74
-55
lines changed

9 files changed

+74
-55
lines changed

Include/cpython/warnings.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,3 @@ 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);

Include/internal/pycore_compile.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ extern int _PyAST_Preprocess(
4949
PyObject *filename,
5050
int optimize,
5151
int ff_features,
52-
int syntax_check_only);
52+
int syntax_check_only,
53+
int enable_warnings);
5354

5455

5556
typedef struct {

Lib/test/test_compile.py

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1664,22 +1664,21 @@ class WeirdDict(dict):
16641664
self.assertRaises(NameError, ns['foo'])
16651665

16661666
def test_compile_warnings(self):
1667-
# See gh-131927
1668-
# Compile warnings originating from the same file and
1669-
# line are now only emitted once.
1667+
# Each invocation of compile() emits compiler warnings, even if they
1668+
# have the same message and line number.
1669+
source = textwrap.dedent(r"""
1670+
# tokenizer
1671+
1or 0 # line 3
1672+
# code generator
1673+
1 is 1 # line 5
1674+
""")
16701675
with warnings.catch_warnings(record=True) as caught:
16711676
warnings.simplefilter("default")
1672-
compile('1 is 1', '<stdin>', 'eval')
1673-
compile('1 is 1', '<stdin>', 'eval')
1674-
1675-
self.assertEqual(len(caught), 1)
1676-
1677-
with warnings.catch_warnings(record=True) as caught:
1678-
warnings.simplefilter("always")
1679-
compile('1 is 1', '<stdin>', 'eval')
1680-
compile('1 is 1', '<stdin>', 'eval')
1677+
for i in range(2):
1678+
# Even if compile() is at the same line.
1679+
compile(source, '<stdin>', 'exec')
16811680

1682-
self.assertEqual(len(caught), 2)
1681+
self.assertEqual([wm.lineno for wm in caught], [3, 5] * 2)
16831682

16841683
def test_compile_warning_in_finally(self):
16851684
# Ensure that warnings inside finally blocks are
@@ -1690,16 +1689,46 @@ def test_compile_warning_in_finally(self):
16901689
try:
16911690
pass
16921691
finally:
1693-
1 is 1
1692+
1 is 1 # line 5
1693+
try:
1694+
pass
1695+
finally: # nested
1696+
1 is 1 # line 9
16941697
""")
16951698

16961699
with warnings.catch_warnings(record=True) as caught:
1697-
warnings.simplefilter("default")
1700+
warnings.simplefilter("always")
1701+
compile(source, '<stdin>', 'exec')
1702+
1703+
self.assertEqual(sorted(wm.lineno for wm in caught), [5, 9])
1704+
for wm in caught:
1705+
self.assertEqual(wm.category, SyntaxWarning)
1706+
self.assertIn("\"is\" with 'int' literal", str(wm.message))
1707+
1708+
# Other code path is used for "try" with "except*".
1709+
source = textwrap.dedent("""
1710+
try:
1711+
pass
1712+
except *Exception:
1713+
pass
1714+
finally:
1715+
1 is 1 # line 7
1716+
try:
1717+
pass
1718+
except *Exception:
1719+
pass
1720+
finally: # nested
1721+
1 is 1 # line 13
1722+
""")
1723+
1724+
with warnings.catch_warnings(record=True) as caught:
1725+
warnings.simplefilter("always")
16981726
compile(source, '<stdin>', 'exec')
16991727

1700-
self.assertEqual(len(caught), 1)
1701-
self.assertEqual(caught[0].category, SyntaxWarning)
1702-
self.assertIn("\"is\" with 'int' literal", str(caught[0].message))
1728+
self.assertEqual(sorted(wm.lineno for wm in caught), [7, 13])
1729+
for wm in caught:
1730+
self.assertEqual(wm.category, SyntaxWarning)
1731+
self.assertIn("\"is\" with 'int' literal", str(wm.message))
17031732

17041733
class TestBooleanExpression(unittest.TestCase):
17051734
class Value:

Lib/test/test_pyrepl/test_interact.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ def f():
293293
""")
294294

295295
with warnings.catch_warnings(record=True) as caught:
296-
warnings.simplefilter("default")
296+
warnings.simplefilter("always")
297297
console.runsource(code)
298298

299299
count = sum("'return' in a 'finally' block" in str(w.message)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix swallowing some syntax warnings in different modules if they
2+
accidentally have the same message and are emitted from the same line.
3+
:func:`ast.parse` no longer emits syntax warnings for
4+
``return``/``break``/``continue`` in ``finally`` (see :pep:`765`) -- they are
5+
only emitted during compilation.

Python/_warnings.c

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,28 +1473,6 @@ PyErr_WarnExplicitObject(PyObject *category, PyObject *message,
14731473
return 0;
14741474
}
14751475

1476-
/* Like PyErr_WarnExplicitObject, but automatically sets up context */
1477-
int
1478-
_PyErr_WarnExplicitObjectWithContext(PyObject *category, PyObject *message,
1479-
PyObject *filename, int lineno)
1480-
{
1481-
PyObject *unused_filename, *module, *registry;
1482-
int unused_lineno;
1483-
int stack_level = 1;
1484-
1485-
if (!setup_context(stack_level, NULL, &unused_filename, &unused_lineno,
1486-
&module, &registry)) {
1487-
return -1;
1488-
}
1489-
1490-
int rc = PyErr_WarnExplicitObject(category, message, filename, lineno,
1491-
module, registry);
1492-
Py_DECREF(unused_filename);
1493-
Py_DECREF(registry);
1494-
Py_DECREF(module);
1495-
return rc;
1496-
}
1497-
14981476
int
14991477
PyErr_WarnExplicit(PyObject *category, const char *text,
15001478
const char *filename_str, int lineno,

Python/ast_preprocess.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ typedef struct {
1919
int optimize;
2020
int ff_features;
2121
int syntax_check_only;
22+
int enable_warnings;
2223

2324
_Py_c_array_t cf_finally; /* context for PEP 765 check */
2425
int cf_finally_used;
@@ -78,7 +79,7 @@ control_flow_in_finally_warning(const char *kw, stmt_ty n, _PyASTPreprocessState
7879
static int
7980
before_return(_PyASTPreprocessState *state, stmt_ty node_)
8081
{
81-
if (state->cf_finally_used > 0) {
82+
if (state->enable_warnings && state->cf_finally_used > 0) {
8283
ControlFlowInFinallyContext *ctx = get_cf_finally_top(state);
8384
if (ctx->in_finally && ! ctx->in_funcdef) {
8485
if (!control_flow_in_finally_warning("return", node_, state)) {
@@ -92,7 +93,7 @@ before_return(_PyASTPreprocessState *state, stmt_ty node_)
9293
static int
9394
before_loop_exit(_PyASTPreprocessState *state, stmt_ty node_, const char *kw)
9495
{
95-
if (state->cf_finally_used > 0) {
96+
if (state->enable_warnings && state->cf_finally_used > 0) {
9697
ControlFlowInFinallyContext *ctx = get_cf_finally_top(state);
9798
if (ctx->in_finally && ! ctx->in_loop) {
9899
if (!control_flow_in_finally_warning(kw, node_, state)) {
@@ -968,14 +969,15 @@ astfold_type_param(type_param_ty node_, PyArena *ctx_, _PyASTPreprocessState *st
968969

969970
int
970971
_PyAST_Preprocess(mod_ty mod, PyArena *arena, PyObject *filename, int optimize,
971-
int ff_features, int syntax_check_only)
972+
int ff_features, int syntax_check_only, int enable_warnings)
972973
{
973974
_PyASTPreprocessState state;
974975
memset(&state, 0, sizeof(_PyASTPreprocessState));
975976
state.filename = filename;
976977
state.optimize = optimize;
977978
state.ff_features = ff_features;
978979
state.syntax_check_only = syntax_check_only;
980+
state.enable_warnings = enable_warnings;
979981
if (_Py_CArray_Init(&state.cf_finally, sizeof(ControlFlowInFinallyContext), 20) < 0) {
980982
return -1;
981983
}

Python/compile.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ typedef struct _PyCompiler {
103103
bool c_save_nested_seqs; /* if true, construct recursive instruction sequences
104104
* (including instructions for nested code objects)
105105
*/
106+
int c_disable_warning;
106107
} compiler;
107108

108109
static int
@@ -135,7 +136,7 @@ compiler_setup(compiler *c, mod_ty mod, PyObject *filename,
135136
c->c_optimize = (optimize == -1) ? _Py_GetConfig()->optimization_level : optimize;
136137
c->c_save_nested_seqs = false;
137138

138-
if (!_PyAST_Preprocess(mod, arena, filename, c->c_optimize, merged, 0)) {
139+
if (!_PyAST_Preprocess(mod, arena, filename, c->c_optimize, merged, 0, 1)) {
139140
return ERROR;
140141
}
141142
c->c_st = _PySymtable_Build(mod, filename, &c->c_future);
@@ -765,6 +766,9 @@ _PyCompile_PushFBlock(compiler *c, location loc,
765766
f->fb_loc = loc;
766767
f->fb_exit = exit;
767768
f->fb_datum = datum;
769+
if (t == COMPILE_FBLOCK_FINALLY_END) {
770+
c->c_disable_warning++;
771+
}
768772
return SUCCESS;
769773
}
770774

@@ -776,6 +780,9 @@ _PyCompile_PopFBlock(compiler *c, fblocktype t, jump_target_label block_label)
776780
u->u_nfblocks--;
777781
assert(u->u_fblock[u->u_nfblocks].fb_type == t);
778782
assert(SAME_JUMP_TARGET_LABEL(u->u_fblock[u->u_nfblocks].fb_block, block_label));
783+
if (t == COMPILE_FBLOCK_FINALLY_END) {
784+
c->c_disable_warning--;
785+
}
779786
}
780787

781788
fblockinfo *
@@ -1203,6 +1210,9 @@ _PyCompile_Error(compiler *c, location loc, const char *format, ...)
12031210
int
12041211
_PyCompile_Warn(compiler *c, location loc, const char *format, ...)
12051212
{
1213+
if (c->c_disable_warning) {
1214+
return 0;
1215+
}
12061216
va_list vargs;
12071217
va_start(vargs, format);
12081218
PyObject *msg = PyUnicode_FromFormatV(format, vargs);
@@ -1492,7 +1502,7 @@ _PyCompile_AstPreprocess(mod_ty mod, PyObject *filename, PyCompilerFlags *cf,
14921502
if (optimize == -1) {
14931503
optimize = _Py_GetConfig()->optimization_level;
14941504
}
1495-
if (!_PyAST_Preprocess(mod, arena, filename, optimize, flags, no_const_folding)) {
1505+
if (!_PyAST_Preprocess(mod, arena, filename, optimize, flags, no_const_folding, 0)) {
14961506
return -1;
14971507
}
14981508
return 0;

Python/errors.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,8 +1962,8 @@ int
19621962
_PyErr_EmitSyntaxWarning(PyObject *msg, PyObject *filename, int lineno, int col_offset,
19631963
int end_lineno, int end_col_offset)
19641964
{
1965-
if (_PyErr_WarnExplicitObjectWithContext(PyExc_SyntaxWarning, msg,
1966-
filename, lineno) < 0)
1965+
if (PyErr_WarnExplicitObject(PyExc_SyntaxWarning, msg,
1966+
filename, lineno, NULL, NULL) < 0)
19671967
{
19681968
if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) {
19691969
/* Replace the SyntaxWarning exception with a SyntaxError

0 commit comments

Comments
 (0)