diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index 5c3b1250ceb045..c11b695f01b8cb 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -14,6 +14,8 @@ from test.support import import_helper from test.support import os_helper from test.support import warnings_helper +from test.support import script_helper +from _pyrepl.completing_reader import stripcolor as strip_ansi from test.support import force_not_colorized from test.support.script_helper import assert_python_ok, assert_python_failure @@ -771,15 +773,35 @@ def test_improper_input(self): self.assertRaises(UserWarning, self.module.warn, 'convert to error') def test_import_from_module(self): - with self.module.catch_warnings(): - self.module._setoption('ignore::Warning') - with self.assertRaises(self.module._OptionError): - self.module._setoption('ignore::TestWarning') - with self.assertRaises(self.module._OptionError): - self.module._setoption('ignore::test.test_warnings.bogus') - self.module._setoption('error::test.test_warnings.TestWarning') - with self.assertRaises(TestWarning): - self.module.warn('test warning', TestWarning) + with os_helper.temp_dir() as script_dir: + script = script_helper.make_script(script_dir, + 'test_warnings_importer', + 'import test.test_warnings.data.import_warning') + rc, out, err = assert_python_ok(script) + self.assertNotIn(b'UserWarning', err) + + def test_syntax_warning_for_compiler(self): + # Test that SyntaxWarning from the compiler has a proper module name, + # not a guessed one like 'sys'. gh-135801 + code = textwrap.dedent("""\ + class A: + def func(self): + return self.var is 2 + """) + # The name of the script is 'test_sw' + with os_helper.temp_dir() as script_dir: + script_name = script_helper.make_script(script_dir, 'test_sw', code) + # We want to check that the warning filter for 'test_sw' module works. + rc, out, err = assert_python_failure("-W", "error::SyntaxWarning:test_sw", + script_name) + self.assertEqual(rc, 1) + self.assertEqual(out, b'') + # Check that we got a SyntaxError. + err = err.decode() + err = strip_ansi(err) + self.assertIn("""SyntaxError: "is" with 'int' literal. Did you mean "=="?""", err) + # Check that the filename in the traceback is correct. + self.assertIn(os.path.basename(script_name), err) class CWCmdLineTests(WCmdLineTests, unittest.TestCase): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-06-23-10-56-32.gh-issue-135801.fix-warning-dedup.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-06-23-10-56-32.gh-issue-135801.fix-warning-dedup.rst new file mode 100644 index 00000000000000..831d0edcfea6d9 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-06-23-10-56-32.gh-issue-135801.fix-warning-dedup.rst @@ -0,0 +1 @@ +Fix warning deduplication regression for SyntaxWarnings with pseudo-filenames (e.g., , ). diff --git a/Python/_warnings.c b/Python/_warnings.c index 12e6172b0cf828..3a53ce297c6f74 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -616,33 +616,60 @@ already_warned(PyInterpreterState *interp, PyObject *registry, PyObject *key, static PyObject * normalize_module(PyObject *filename) { - PyObject *module; - int kind; - const void *data; - Py_ssize_t len; - - len = PyUnicode_GetLength(filename); - if (len < 0) + PyObject *module_name = NULL; + PyObject *os_path = NULL; + PyObject *basename = NULL; + PyObject *splitext = NULL; + PyObject *root = NULL; + + os_path = PyImport_ImportModule("os.path"); + if (os_path == NULL) { return NULL; + } - if (len == 0) - return PyUnicode_FromString(""); + basename = PyObject_CallMethod(os_path, "basename", "O", filename); + if (basename == NULL) { + goto cleanup; + } - kind = PyUnicode_KIND(filename); - data = PyUnicode_DATA(filename); + splitext = PyObject_CallMethod(os_path, "splitext", "O", basename); + if (splitext == NULL) { + goto cleanup; + } - /* if filename.endswith(".py"): */ - if (len >= 3 && - PyUnicode_READ(kind, data, len-3) == '.' && - PyUnicode_READ(kind, data, len-2) == 'p' && - PyUnicode_READ(kind, data, len-1) == 'y') - { - module = PyUnicode_Substring(filename, 0, len-3); + root = PyTuple_GetItem(splitext, 0); + if (root == NULL) { + goto cleanup; } - else { - module = Py_NewRef(filename); + + if (PyUnicode_CompareWithASCIIString(root, "__init__") == 0) { + PyObject *dirname = PyObject_CallMethod(os_path, "dirname", "O", filename); + if (dirname == NULL) { + goto cleanup; + } + module_name = PyObject_CallMethod(os_path, "basename", "O", dirname); + Py_DECREF(dirname); + } else { + module_name = Py_NewRef(root); + } + +cleanup: + Py_XDECREF(os_path); + Py_XDECREF(basename); + Py_XDECREF(splitext); + + if (module_name == NULL) { + // Fallback or error occurred + PyErr_Clear(); + return PyUnicode_FromString(""); } - return module; + + if (PyUnicode_GetLength(module_name) == 0) { + Py_DECREF(module_name); + return PyUnicode_FromString(""); + } + + return module_name; } static int diff --git a/Python/errors.c b/Python/errors.c index a3122f76bdd87d..7eb69378f972c1 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1,4 +1,3 @@ - /* Error handling */ #include "Python.h" @@ -1937,8 +1936,32 @@ int _PyErr_EmitSyntaxWarning(PyObject *msg, PyObject *filename, int lineno, int col_offset, int end_lineno, int end_col_offset) { - if (_PyErr_WarnExplicitObjectWithContext(PyExc_SyntaxWarning, msg, - filename, lineno) < 0) + /* For pseudo-filenames (e.g., , ), use the original approach + to maintain compatibility with existing behavior */ + Py_ssize_t len = PyUnicode_GET_LENGTH(filename); + if (len > 1 && + PyUnicode_READ_CHAR(filename, 0) == '<' && + PyUnicode_READ_CHAR(filename, len - 1) == '>') + { + if (_PyErr_WarnExplicitObjectWithContext(PyExc_SyntaxWarning, msg, + filename, lineno) < 0) + { + if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) { + /* Replace the SyntaxWarning exception with a SyntaxError + to get a more accurate error report */ + PyErr_Clear(); + _PyErr_RaiseSyntaxError(msg, filename, lineno, col_offset, + end_lineno, end_col_offset); + } + return -1; + } + return 0; + } + + /* For regular files, derive the module from the filename by passing NULL + as the module argument to PyErr_WarnExplicitObject */ + if (PyErr_WarnExplicitObject(PyExc_SyntaxWarning, msg, + filename, lineno, NULL, NULL) < 0) { if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) { /* Replace the SyntaxWarning exception with a SyntaxError