]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-98401: Invalid escape sequences emits SyntaxWarning (#99011)
authorVictor Stinner <vstinner@python.org>
Thu, 3 Nov 2022 16:53:25 +0000 (17:53 +0100)
committerGitHub <noreply@github.com>
Thu, 3 Nov 2022 16:53:25 +0000 (17:53 +0100)
A backslash-character pair that is not a valid escape sequence now
generates a SyntaxWarning, instead of DeprecationWarning.  For
example, re.compile("\d+\.\d+") now emits a SyntaxWarning ("\d" is an
invalid escape sequence), use raw strings for regular expression:
re.compile(r"\d+\.\d+"). In a future Python version, SyntaxError will
eventually be raised, instead of SyntaxWarning.

Octal escapes with value larger than 0o377 (ex: "\477"), deprecated
in Python 3.11, now produce a SyntaxWarning, instead of
DeprecationWarning. In a future Python version they will be
eventually a SyntaxError.

codecs.escape_decode() and codecs.unicode_escape_decode() are left
unchanged: they still emit DeprecationWarning.

* The parser only emits SyntaxWarning for Python 3.12 (feature
  version), and still emits DeprecationWarning on older Python
  versions.
* Fix SyntaxWarning by using raw strings in Tools/c-analyzer/ and
  wasm_build.py.

Doc/library/re.rst
Doc/reference/lexical_analysis.rst
Doc/whatsnew/3.12.rst
Lib/test/test_codeop.py
Lib/test/test_fstring.py
Lib/test/test_string_literals.py
Misc/NEWS.d/next/Core and Builtins/2022-11-02-17-02-06.gh-issue-98401.y-dbVW.rst [new file with mode: 0644]
Misc/NEWS.d/next/Core and Builtins/2022-11-03-13-11-17.gh-issue-98401.CBS4nv.rst [new file with mode: 0644]
Parser/string_parser.c
Tools/c-analyzer/c_parser/_state_machine.py
Tools/wasm/wasm_build.py

index 0e7dda04b1d68d522eb88d3ab613375fa428fbea..0034b46fb1ced2620a1b536eb5036e456a4714d6 100644 (file)
@@ -29,7 +29,7 @@ a literal backslash, one might have to write ``'\\\\'`` as the pattern
 string, because the regular expression must be ``\\``, and each
 backslash must be expressed as ``\\`` inside a regular Python string
 literal. Also, please note that any invalid escape sequences in Python's
-usage of the backslash in string literals now generate a :exc:`DeprecationWarning`
+usage of the backslash in string literals now generate a :exc:`SyntaxWarning`
 and in the future this will become a :exc:`SyntaxError`. This behaviour
 will happen even if it is a valid escape sequence for a regular expression.
 
index 4ab6e90a6234495d559b4c74ecbd2c2b8723d12a..8adb4b740825d01009f463197152d717d3b21191 100644 (file)
@@ -612,9 +612,13 @@ Notes:
    As in Standard C, up to three octal digits are accepted.
 
    .. versionchanged:: 3.11
-      Octal escapes with value larger than ``0o377`` produce a :exc:`DeprecationWarning`.
-      In a future Python version they will be a :exc:`SyntaxWarning` and
-      eventually a :exc:`SyntaxError`.
+      Octal escapes with value larger than ``0o377`` produce a
+      :exc:`DeprecationWarning`.
+
+   .. versionchanged:: 3.12
+      Octal escapes with value larger than ``0o377`` produce a
+      :exc:`SyntaxWarning`. In a future Python version they will be eventually
+      a :exc:`SyntaxError`.
 
 (3)
    Unlike in Standard C, exactly two hex digits are required.
@@ -646,9 +650,11 @@ escape sequences only recognized in string literals fall into the category of
 unrecognized escapes for bytes literals.
 
    .. versionchanged:: 3.6
-      Unrecognized escape sequences produce a :exc:`DeprecationWarning`.  In
-      a future Python version they will be a :exc:`SyntaxWarning` and
-      eventually a :exc:`SyntaxError`.
+      Unrecognized escape sequences produce a :exc:`DeprecationWarning`.
+
+   .. versionchanged:: 3.12
+      Unrecognized escape sequences produce a :exc:`SyntaxWarning`. In a future
+      Python version they will be eventually a :exc:`SyntaxError`.
 
 Even in a raw literal, quotes can be escaped with a backslash, but the
 backslash remains in the result; for example, ``r"\""`` is a valid string
index a9432561f3fc500d1ffa398721ae895690a9612c..73c124d9e052929497725aa79fd3ba2f6ee9836f 100644 (file)
@@ -121,6 +121,22 @@ Other Language Changes
   chance to execute the GC periodically. (Contributed by Pablo Galindo in
   :gh:`97922`.)
 
+* A backslash-character pair that is not a valid escape sequence now generates
+  a :exc:`SyntaxWarning`, instead of :exc:`DeprecationWarning`.
+  For example, ``re.compile("\d+\.\d+")`` now emits a :exc:`SyntaxWarning`
+  (``"\d"`` is an invalid escape sequence), use raw strings for regular
+  expression: ``re.compile(r"\d+\.\d+")``.
+  In a future Python version, :exc:`SyntaxError` will eventually be raised,
+  instead of :exc:`SyntaxWarning`.
+  (Contributed by Victor Stinner in :gh:`98401`.)
+
+* Octal escapes with value larger than ``0o377`` (ex: ``"\477"``), deprecated
+  in Python 3.11, now produce a :exc:`SyntaxWarning`, instead of
+  :exc:`DeprecationWarning`.
+  In a future Python version they will be eventually a :exc:`SyntaxError`.
+  (Contributed by Victor Stinner in :gh:`98401`.)
+
+
 New Modules
 ===========
 
index 133096d25a44bc24468994a162e9ef9008ff6190..d7b51be642e46f72bdcf4d8490d5c256e9bee5c0 100644 (file)
@@ -310,8 +310,8 @@ class CodeopTests(unittest.TestCase):
     def test_warning(self):
         # Test that the warning is only returned once.
         with warnings_helper.check_warnings(
-                (".*literal", SyntaxWarning),
-                (".*invalid", DeprecationWarning),
+                ('"is" with a literal', SyntaxWarning),
+                ("invalid escape sequence", SyntaxWarning),
                 ) as w:
             compile_command(r"'\e' is 0")
             self.assertEqual(len(w.warnings), 2)
@@ -321,9 +321,9 @@ class CodeopTests(unittest.TestCase):
             warnings.simplefilter('error', SyntaxWarning)
             compile_command('1 is 1', symbol='exec')
 
-        # Check DeprecationWarning treated as an SyntaxError
+        # Check SyntaxWarning treated as an SyntaxError
         with warnings.catch_warnings(), self.assertRaises(SyntaxError):
-            warnings.simplefilter('error', DeprecationWarning)
+            warnings.simplefilter('error', SyntaxWarning)
             compile_command(r"'\e'", symbol='exec')
 
     def test_incomplete_warning(self):
@@ -337,7 +337,7 @@ class CodeopTests(unittest.TestCase):
             warnings.simplefilter('always')
             self.assertInvalid("'\\e' 1")
         self.assertEqual(len(w), 1)
-        self.assertEqual(w[0].category, DeprecationWarning)
+        self.assertEqual(w[0].category, SyntaxWarning)
         self.assertRegex(str(w[0].message), 'invalid escape sequence')
         self.assertEqual(w[0].filename, '<input>')
 
index bf3a5b0bbccdfb968d74f55a14edd73682cc921d..318f38a6ed5b143a96d360a203d0818a5f66e5ba 100644 (file)
@@ -776,7 +776,7 @@ x = (
         self.assertEqual(f'2\x203', '2 3')
         self.assertEqual(f'\x203', ' 3')
 
-        with self.assertWarns(DeprecationWarning):  # invalid escape sequence
+        with self.assertWarns(SyntaxWarning):  # invalid escape sequence
             value = eval(r"f'\{6*7}'")
         self.assertEqual(value, '\\42')
         self.assertEqual(f'\\{6*7}', '\\42')
index 7247b7e48bc2b6f67c3fd15f4047c49784477378..9b663c00223d1b733a2d9672471f40d3bf4869c4 100644 (file)
@@ -109,11 +109,11 @@ class TestLiterals(unittest.TestCase):
         for b in range(1, 128):
             if b in b"""\n\r"'01234567NU\\abfnrtuvx""":
                 continue
-            with self.assertWarns(DeprecationWarning):
+            with self.assertWarns(SyntaxWarning):
                 self.assertEqual(eval(r"'\%c'" % b), '\\' + chr(b))
 
         with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter('always', category=DeprecationWarning)
+            warnings.simplefilter('always', category=SyntaxWarning)
             eval("'''\n\\z'''")
         self.assertEqual(len(w), 1)
         self.assertEqual(str(w[0].message), r"invalid escape sequence '\z'")
@@ -121,7 +121,7 @@ class TestLiterals(unittest.TestCase):
         self.assertEqual(w[0].lineno, 1)
 
         with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter('error', category=DeprecationWarning)
+            warnings.simplefilter('error', category=SyntaxWarning)
             with self.assertRaises(SyntaxError) as cm:
                 eval("'''\n\\z'''")
             exc = cm.exception
@@ -133,11 +133,11 @@ class TestLiterals(unittest.TestCase):
 
     def test_eval_str_invalid_octal_escape(self):
         for i in range(0o400, 0o1000):
-            with self.assertWarns(DeprecationWarning):
+            with self.assertWarns(SyntaxWarning):
                 self.assertEqual(eval(r"'\%o'" % i), chr(i))
 
         with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter('always', category=DeprecationWarning)
+            warnings.simplefilter('always', category=SyntaxWarning)
             eval("'''\n\\407'''")
         self.assertEqual(len(w), 1)
         self.assertEqual(str(w[0].message),
@@ -146,7 +146,7 @@ class TestLiterals(unittest.TestCase):
         self.assertEqual(w[0].lineno, 1)
 
         with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter('error', category=DeprecationWarning)
+            warnings.simplefilter('error', category=SyntaxWarning)
             with self.assertRaises(SyntaxError) as cm:
                 eval("'''\n\\407'''")
             exc = cm.exception
@@ -186,11 +186,11 @@ class TestLiterals(unittest.TestCase):
         for b in range(1, 128):
             if b in b"""\n\r"'01234567\\abfnrtvx""":
                 continue
-            with self.assertWarns(DeprecationWarning):
+            with self.assertWarns(SyntaxWarning):
                 self.assertEqual(eval(r"b'\%c'" % b), b'\\' + bytes([b]))
 
         with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter('always', category=DeprecationWarning)
+            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'")
@@ -198,7 +198,7 @@ class TestLiterals(unittest.TestCase):
         self.assertEqual(w[0].lineno, 1)
 
         with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter('error', category=DeprecationWarning)
+            warnings.simplefilter('error', category=SyntaxWarning)
             with self.assertRaises(SyntaxError) as cm:
                 eval("b'''\n\\z'''")
             exc = cm.exception
@@ -209,11 +209,11 @@ class TestLiterals(unittest.TestCase):
 
     def test_eval_bytes_invalid_octal_escape(self):
         for i in range(0o400, 0o1000):
-            with self.assertWarns(DeprecationWarning):
+            with self.assertWarns(SyntaxWarning):
                 self.assertEqual(eval(r"b'\%o'" % i), bytes([i & 0o377]))
 
         with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter('always', category=DeprecationWarning)
+            warnings.simplefilter('always', category=SyntaxWarning)
             eval("b'''\n\\407'''")
         self.assertEqual(len(w), 1)
         self.assertEqual(str(w[0].message),
@@ -222,7 +222,7 @@ class TestLiterals(unittest.TestCase):
         self.assertEqual(w[0].lineno, 1)
 
         with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter('error', category=DeprecationWarning)
+            warnings.simplefilter('error', category=SyntaxWarning)
             with self.assertRaises(SyntaxError) as cm:
                 eval("b'''\n\\407'''")
             exc = cm.exception
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-11-02-17-02-06.gh-issue-98401.y-dbVW.rst b/Misc/NEWS.d/next/Core and Builtins/2022-11-02-17-02-06.gh-issue-98401.y-dbVW.rst
new file mode 100644 (file)
index 0000000..05b33c2
--- /dev/null
@@ -0,0 +1,7 @@
+A backslash-character pair that is not a valid escape sequence now generates a
+:exc:`SyntaxWarning`, instead of :exc:`DeprecationWarning`.  For example,
+``re.compile("\d+\.\d+")`` now emits a :exc:`SyntaxWarning` (``"\d"`` is an
+invalid escape sequence), use raw strings for regular expression:
+``re.compile(r"\d+\.\d+")``. In a future Python version, :exc:`SyntaxError`
+will eventually be raised, instead of :exc:`SyntaxWarning`. Patch by Victor
+Stinner.
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-11-03-13-11-17.gh-issue-98401.CBS4nv.rst b/Misc/NEWS.d/next/Core and Builtins/2022-11-03-13-11-17.gh-issue-98401.CBS4nv.rst
new file mode 100644 (file)
index 0000000..fbfec10
--- /dev/null
@@ -0,0 +1,4 @@
+Octal escapes with value larger than ``0o377`` (ex: ``"\477"``), deprecated
+in Python 3.11, now produce a :exc:`SyntaxWarning`, instead of
+:exc:`DeprecationWarning`. In a future Python version they will be
+eventually a :exc:`SyntaxError`. Patch by Victor Stinner.
index 9bc3b082136be53a6ae0068af91cc353cd1f4e49..e13272c17ca301755b7c289edc315306a4eef011 100644 (file)
@@ -21,9 +21,16 @@ warn_invalid_escape_sequence(Parser *p, const char *first_invalid_escape, Token
     if (msg == NULL) {
         return -1;
     }
-    if (PyErr_WarnExplicitObject(PyExc_DeprecationWarning, msg, p->tok->filename,
+    PyObject *category;
+    if (p->feature_version >= 12) {
+        category = PyExc_SyntaxWarning;
+    }
+    else {
+        category = PyExc_DeprecationWarning;
+    }
+    if (PyErr_WarnExplicitObject(category, msg, p->tok->filename,
                                  t->lineno, NULL, NULL) < 0) {
-        if (PyErr_ExceptionMatches(PyExc_DeprecationWarning)) {
+        if (PyErr_ExceptionMatches(category)) {
             /* Replace the DeprecationWarning exception with a SyntaxError
                to get a more accurate error report */
             PyErr_Clear();
index 53cbb13e7c4edb375a4ca728f02155195d76ee43..875323188aadfdc6a59d0b4cd57807f18b63e17c 100644 (file)
@@ -96,7 +96,7 @@ def parse(srclines):
 #    # end matched parens
 #    ''')
 
-'''
+r'''
         # for loop
         (?:
             \s* \b for
index 63812c6f3153f24aedb52322dffc9df86ba85fdf..493682c5b138a3dc3ff6e5976b5209de28c4c259 100755 (executable)
@@ -137,7 +137,7 @@ def read_python_version(configure: pathlib.Path = CONFIGURE) -> str:
     configure and configure.ac are the canonical source for major and
     minor version number.
     """
-    version_re = re.compile("^PACKAGE_VERSION='(\d\.\d+)'")
+    version_re = re.compile(r"^PACKAGE_VERSION='(\d\.\d+)'")
     with configure.open(encoding="utf-8") as f:
         for line in f:
             mo = version_re.match(line)