]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-128016: Improved invalid escape sequence warning message (#128020)
authorUmar Butler <8473183+umarbutler@users.noreply.github.com>
Wed, 15 Jan 2025 17:00:54 +0000 (04:00 +1100)
committerGitHub <noreply@github.com>
Wed, 15 Jan 2025 17:00:54 +0000 (18:00 +0100)
Lib/test/test_cmd_line_script.py
Lib/test/test_codeop.py
Lib/test/test_string_literals.py
Lib/test/test_unparse.py
Misc/NEWS.d/next/Core_and_Builtins/2024-12-17-09-28-17.gh-issue-128016.DPqhah.rst [new file with mode: 0644]
Objects/bytesobject.c
Objects/unicodeobject.c
Parser/string_parser.c
Parser/tokenizer/helpers.c

index f30107225ff612a2466c596500161e28fb1438fa..527d51857fc904b517b8a2bbf50f46e342bc2604 100644 (file)
@@ -659,7 +659,8 @@ class CmdLineTest(unittest.TestCase):
                 stderr.splitlines()[-3:],
                 [   b'    foo = """\\q"""',
                     b'          ^^^^^^^^',
-                    b'SyntaxError: invalid escape sequence \'\\q\''
+                    b'SyntaxError: "\\q" is an invalid escape sequence. '
+                    b'Did you mean "\\\\q"? A raw string is also an option.'
                 ],
             )
 
index 787bd1b6a79e20b8df91ab36a425ff1eb96eb591..0eefc22d11bce0eb599946efa5c6edb2e4344a61 100644 (file)
@@ -282,7 +282,7 @@ class CodeopTests(unittest.TestCase):
         # Test that the warning is only returned once.
         with warnings_helper.check_warnings(
                 ('"is" with \'str\' literal', SyntaxWarning),
-                ("invalid escape sequence", SyntaxWarning),
+                ('"\\\\e" is an invalid escape sequence', SyntaxWarning),
                 ) as w:
             compile_command(r"'\e' is 0")
             self.assertEqual(len(w.warnings), 2)
index c7c6f684cd33f0777abd83c1d56e3cc2b5bc6a33..f56195ca27672c34706a4550c96fd2f55d604ea2 100644 (file)
@@ -116,7 +116,9 @@ class TestLiterals(unittest.TestCase):
             warnings.simplefilter('always', category=SyntaxWarning)
             eval("'''\n\\z'''")
         self.assertEqual(len(w), 1)
-        self.assertEqual(str(w[0].message), r"invalid escape sequence '\z'")
+        self.assertEqual(str(w[0].message), r'"\z" is an invalid escape sequence. '
+                         r'Such sequences will not work in the future. '
+                         r'Did you mean "\\z"? A raw string is also an option.')
         self.assertEqual(w[0].filename, '<string>')
         self.assertEqual(w[0].lineno, 1)
 
@@ -126,7 +128,8 @@ class TestLiterals(unittest.TestCase):
                 eval("'''\n\\z'''")
             exc = cm.exception
         self.assertEqual(w, [])
-        self.assertEqual(exc.msg, r"invalid escape sequence '\z'")
+        self.assertEqual(exc.msg, r'"\z" is an invalid escape sequence. '
+                         r'Did you mean "\\z"? A raw string is also an option.')
         self.assertEqual(exc.filename, '<string>')
         self.assertEqual(exc.lineno, 1)
         self.assertEqual(exc.offset, 1)
@@ -153,7 +156,9 @@ class TestLiterals(unittest.TestCase):
             eval("'''\n\\407'''")
         self.assertEqual(len(w), 1)
         self.assertEqual(str(w[0].message),
-                         r"invalid octal escape sequence '\407'")
+                         r'"\407" is an invalid octal escape sequence. '
+                         r'Such sequences will not work in the future. '
+                         r'Did you mean "\\407"? A raw string is also an option.')
         self.assertEqual(w[0].filename, '<string>')
         self.assertEqual(w[0].lineno, 1)
 
@@ -163,7 +168,8 @@ class TestLiterals(unittest.TestCase):
                 eval("'''\n\\407'''")
             exc = cm.exception
         self.assertEqual(w, [])
-        self.assertEqual(exc.msg, r"invalid octal escape sequence '\407'")
+        self.assertEqual(exc.msg, r'"\407" is an invalid octal escape sequence. '
+                                 r'Did you mean "\\407"? A raw string is also an option.')
         self.assertEqual(exc.filename, '<string>')
         self.assertEqual(exc.lineno, 1)
         self.assertEqual(exc.offset, 1)
@@ -205,7 +211,9 @@ class TestLiterals(unittest.TestCase):
             warnings.simplefilter('always', category=SyntaxWarning)
             eval("b'''\n\\z'''")
         self.assertEqual(len(w), 1)
-        self.assertEqual(str(w[0].message), r"invalid escape sequence '\z'")
+        self.assertEqual(str(w[0].message), r'"\z" is an invalid escape sequence. '
+                         r'Such sequences will not work in the future. '
+                         r'Did you mean "\\z"? A raw string is also an option.')
         self.assertEqual(w[0].filename, '<string>')
         self.assertEqual(w[0].lineno, 1)
 
@@ -215,7 +223,8 @@ class TestLiterals(unittest.TestCase):
                 eval("b'''\n\\z'''")
             exc = cm.exception
         self.assertEqual(w, [])
-        self.assertEqual(exc.msg, r"invalid escape sequence '\z'")
+        self.assertEqual(exc.msg, r'"\z" is an invalid escape sequence. '
+                         r'Did you mean "\\z"? A raw string is also an option.')
         self.assertEqual(exc.filename, '<string>')
         self.assertEqual(exc.lineno, 1)
 
@@ -228,8 +237,9 @@ class TestLiterals(unittest.TestCase):
             warnings.simplefilter('always', category=SyntaxWarning)
             eval("b'''\n\\407'''")
         self.assertEqual(len(w), 1)
-        self.assertEqual(str(w[0].message),
-                         r"invalid octal escape sequence '\407'")
+        self.assertEqual(str(w[0].message), r'"\407" is an invalid octal escape sequence. '
+                         r'Such sequences will not work in the future. '
+                         r'Did you mean "\\407"? A raw string is also an option.')
         self.assertEqual(w[0].filename, '<string>')
         self.assertEqual(w[0].lineno, 1)
 
@@ -239,7 +249,8 @@ class TestLiterals(unittest.TestCase):
                 eval("b'''\n\\407'''")
             exc = cm.exception
         self.assertEqual(w, [])
-        self.assertEqual(exc.msg, r"invalid octal escape sequence '\407'")
+        self.assertEqual(exc.msg, r'"\407" is an invalid octal escape sequence. '
+                         r'Did you mean "\\407"? A raw string is also an option.')
         self.assertEqual(exc.filename, '<string>')
         self.assertEqual(exc.lineno, 1)
 
index 35394f29fbe49dfb2c33d131a187e5270ee0eb0a..332919540da4d6217aea7fe5e05a8b576c13a6d4 100644 (file)
@@ -651,7 +651,9 @@ class CosmeticTestCase(ASTTestCase):
 
     def test_backslash_in_format_spec(self):
         import re
-        msg = re.escape("invalid escape sequence '\\ '")
+        msg = re.escape('"\\ " is an invalid escape sequence. '
+                        'Such sequences will not work in the future. '
+                        'Did you mean "\\\\ "? A raw string is also an option.')
         with self.assertWarnsRegex(SyntaxWarning, msg):
             self.check_ast_roundtrip("""f"{x:\\ }" """)
         self.check_ast_roundtrip("""f"{x:\\n}" """)
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-12-17-09-28-17.gh-issue-128016.DPqhah.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-17-09-28-17.gh-issue-128016.DPqhah.rst
new file mode 100644 (file)
index 0000000..0832d77
--- /dev/null
@@ -0,0 +1 @@
+Improved the ``SyntaxWarning`` message for invalid escape sequences to clarify that such sequences will raise a ``SyntaxError`` in future Python releases. The new message also suggests a potential fix, i.e., ``Did you mean "\\e"?``.
index 024653546563e642253cda06892c7cdfdf80cee4..b3d1c425ad18b7b19c9f289a2b16ee92bd837c92 100644 (file)
@@ -1205,7 +1205,8 @@ PyObject *PyBytes_DecodeEscape(const char *s,
         unsigned char c = *first_invalid_escape;
         if ('4' <= c && c <= '7') {
             if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
-                                 "invalid octal escape sequence '\\%.3s'",
+                                 "b\"\\%.3s\" is an invalid octal escape sequence. "
+                                 "Such sequences will not work in the future. ",
                                  first_invalid_escape) < 0)
             {
                 Py_DECREF(result);
@@ -1214,7 +1215,8 @@ PyObject *PyBytes_DecodeEscape(const char *s,
         }
         else {
             if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
-                                 "invalid escape sequence '\\%c'",
+                                 "b\"\\%c\" is an invalid escape sequence. "
+                                 "Such sequences will not work in the future. ",
                                  c) < 0)
             {
                 Py_DECREF(result);
@@ -1223,7 +1225,6 @@ PyObject *PyBytes_DecodeEscape(const char *s,
         }
     }
     return result;
-
 }
 /* -------------------------------------------------------------------- */
 /* object api */
index 3eafa2381c1a4d501b142ceff5dde416cf66254a..d9952f764bb178a41de7340c45d699a881a4534c 100644 (file)
@@ -6853,7 +6853,8 @@ _PyUnicode_DecodeUnicodeEscapeStateful(const char *s,
         unsigned char c = *first_invalid_escape;
         if ('4' <= c && c <= '7') {
             if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
-                                 "invalid octal escape sequence '\\%.3s'",
+                                 "\"\\%.3s\" is an invalid octal escape sequence. "
+                                 "Such sequences will not work in the future. ",
                                  first_invalid_escape) < 0)
             {
                 Py_DECREF(result);
@@ -6862,7 +6863,8 @@ _PyUnicode_DecodeUnicodeEscapeStateful(const char *s,
         }
         else {
             if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
-                                 "invalid escape sequence '\\%c'",
+                                 "\"\\%c\" is an invalid escape sequence. "
+                                 "Such sequences will not work in the future. ",
                                  c) < 0)
             {
                 Py_DECREF(result);
index 9537c543b0eb939248d0158c4f8d15be823f592f..9dd8f9ef28bd4f5a7a476661631abae1f29f60e6 100644 (file)
@@ -28,9 +28,16 @@ warn_invalid_escape_sequence(Parser *p, const char *first_invalid_escape, Token
     int octal = ('4' <= c && c <= '7');
     PyObject *msg =
         octal
-        ? PyUnicode_FromFormat("invalid octal escape sequence '\\%.3s'",
-                               first_invalid_escape)
-        : PyUnicode_FromFormat("invalid escape sequence '\\%c'", c);
+        ? PyUnicode_FromFormat(
+              "\"\\%.3s\" is an invalid octal escape sequence. "
+              "Such sequences will not work in the future. "
+              "Did you mean \"\\\\%.3s\"? A raw string is also an option.",
+              first_invalid_escape, first_invalid_escape)
+        : PyUnicode_FromFormat(
+              "\"\\%c\" is an invalid escape sequence. "
+              "Such sequences will not work in the future. "
+              "Did you mean \"\\\\%c\"? A raw string is also an option.",
+              c, c);
     if (msg == NULL) {
         return -1;
     }
@@ -53,11 +60,16 @@ warn_invalid_escape_sequence(Parser *p, const char *first_invalid_escape, Token
                error location, if p->known_err_token is not set. */
             p->known_err_token = t;
             if (octal) {
-                RAISE_SYNTAX_ERROR("invalid octal escape sequence '\\%.3s'",
-                                   first_invalid_escape);
+                RAISE_SYNTAX_ERROR(
+                    "\"\\%.3s\" is an invalid octal escape sequence. "
+                    "Did you mean \"\\\\%.3s\"? A raw string is also an option.",
+                    first_invalid_escape, first_invalid_escape);
             }
             else {
-                RAISE_SYNTAX_ERROR("invalid escape sequence '\\%c'", c);
+                RAISE_SYNTAX_ERROR(
+                    "\"\\%c\" is an invalid escape sequence. "
+                    "Did you mean \"\\\\%c\"? A raw string is also an option.",
+                    c, c);
             }
         }
         Py_DECREF(msg);
index 9c9d05bbef0f1a70b521cf1583e8fecf7c216a98..5a416adb875aa125a07b8a630691416c11270181 100644 (file)
@@ -113,7 +113,10 @@ _PyTokenizer_warn_invalid_escape_sequence(struct tok_state *tok, int first_inval
     }
 
     PyObject *msg = PyUnicode_FromFormat(
-        "invalid escape sequence '\\%c'",
+        "\"\\%c\" is an invalid escape sequence. "
+        "Such sequences will not work in the future. "
+        "Did you mean \"\\\\%c\"? A raw string is also an option.",
+        (char) first_invalid_escape_char,
         (char) first_invalid_escape_char
     );
 
@@ -129,7 +132,12 @@ _PyTokenizer_warn_invalid_escape_sequence(struct tok_state *tok, int first_inval
             /* Replace the SyntaxWarning exception with a SyntaxError
                to get a more accurate error report */
             PyErr_Clear();
-            return _PyTokenizer_syntaxerror(tok, "invalid escape sequence '\\%c'", (char) first_invalid_escape_char);
+
+            return _PyTokenizer_syntaxerror(tok,
+                "\"\\%c\" is an invalid escape sequence. "
+                "Did you mean \"\\\\%c\"? A raw string is also an option.",
+                (char) first_invalid_escape_char,
+                (char) first_invalid_escape_char);
         }
 
         return -1;