Skip to content

Commit a60ddd3

Browse files
authored
gh-98401: Invalid escape sequences emits SyntaxWarning (#99011)
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.
1 parent 916af11 commit a60ddd3

11 files changed

+69
-29
lines changed

Doc/library/re.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ a literal backslash, one might have to write ``'\\\\'`` as the pattern
2929
string, because the regular expression must be ``\\``, and each
3030
backslash must be expressed as ``\\`` inside a regular Python string
3131
literal. Also, please note that any invalid escape sequences in Python's
32-
usage of the backslash in string literals now generate a :exc:`DeprecationWarning`
32+
usage of the backslash in string literals now generate a :exc:`SyntaxWarning`
3333
and in the future this will become a :exc:`SyntaxError`. This behaviour
3434
will happen even if it is a valid escape sequence for a regular expression.
3535

Doc/reference/lexical_analysis.rst

+12-6
Original file line numberDiff line numberDiff line change
@@ -612,9 +612,13 @@ Notes:
612612
As in Standard C, up to three octal digits are accepted.
613613

614614
.. versionchanged:: 3.11
615-
Octal escapes with value larger than ``0o377`` produce a :exc:`DeprecationWarning`.
616-
In a future Python version they will be a :exc:`SyntaxWarning` and
617-
eventually a :exc:`SyntaxError`.
615+
Octal escapes with value larger than ``0o377`` produce a
616+
:exc:`DeprecationWarning`.
617+
618+
.. versionchanged:: 3.12
619+
Octal escapes with value larger than ``0o377`` produce a
620+
:exc:`SyntaxWarning`. In a future Python version they will be eventually
621+
a :exc:`SyntaxError`.
618622

619623
(3)
620624
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
646650
unrecognized escapes for bytes literals.
647651

648652
.. versionchanged:: 3.6
649-
Unrecognized escape sequences produce a :exc:`DeprecationWarning`. In
650-
a future Python version they will be a :exc:`SyntaxWarning` and
651-
eventually a :exc:`SyntaxError`.
653+
Unrecognized escape sequences produce a :exc:`DeprecationWarning`.
654+
655+
.. versionchanged:: 3.12
656+
Unrecognized escape sequences produce a :exc:`SyntaxWarning`. In a future
657+
Python version they will be eventually a :exc:`SyntaxError`.
652658

653659
Even in a raw literal, quotes can be escaped with a backslash, but the
654660
backslash remains in the result; for example, ``r"\""`` is a valid string

Doc/whatsnew/3.12.rst

+16
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,22 @@ Other Language Changes
121121
chance to execute the GC periodically. (Contributed by Pablo Galindo in
122122
:gh:`97922`.)
123123

124+
* A backslash-character pair that is not a valid escape sequence now generates
125+
a :exc:`SyntaxWarning`, instead of :exc:`DeprecationWarning`.
126+
For example, ``re.compile("\d+\.\d+")`` now emits a :exc:`SyntaxWarning`
127+
(``"\d"`` is an invalid escape sequence), use raw strings for regular
128+
expression: ``re.compile(r"\d+\.\d+")``.
129+
In a future Python version, :exc:`SyntaxError` will eventually be raised,
130+
instead of :exc:`SyntaxWarning`.
131+
(Contributed by Victor Stinner in :gh:`98401`.)
132+
133+
* Octal escapes with value larger than ``0o377`` (ex: ``"\477"``), deprecated
134+
in Python 3.11, now produce a :exc:`SyntaxWarning`, instead of
135+
:exc:`DeprecationWarning`.
136+
In a future Python version they will be eventually a :exc:`SyntaxError`.
137+
(Contributed by Victor Stinner in :gh:`98401`.)
138+
139+
124140
New Modules
125141
===========
126142

Lib/test/test_codeop.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,8 @@ def test_filename(self):
310310
def test_warning(self):
311311
# Test that the warning is only returned once.
312312
with warnings_helper.check_warnings(
313-
(".*literal", SyntaxWarning),
314-
(".*invalid", DeprecationWarning),
313+
('"is" with a literal', SyntaxWarning),
314+
("invalid escape sequence", SyntaxWarning),
315315
) as w:
316316
compile_command(r"'\e' is 0")
317317
self.assertEqual(len(w.warnings), 2)
@@ -321,9 +321,9 @@ def test_warning(self):
321321
warnings.simplefilter('error', SyntaxWarning)
322322
compile_command('1 is 1', symbol='exec')
323323

324-
# Check DeprecationWarning treated as an SyntaxError
324+
# Check SyntaxWarning treated as an SyntaxError
325325
with warnings.catch_warnings(), self.assertRaises(SyntaxError):
326-
warnings.simplefilter('error', DeprecationWarning)
326+
warnings.simplefilter('error', SyntaxWarning)
327327
compile_command(r"'\e'", symbol='exec')
328328

329329
def test_incomplete_warning(self):
@@ -337,7 +337,7 @@ def test_invalid_warning(self):
337337
warnings.simplefilter('always')
338338
self.assertInvalid("'\\e' 1")
339339
self.assertEqual(len(w), 1)
340-
self.assertEqual(w[0].category, DeprecationWarning)
340+
self.assertEqual(w[0].category, SyntaxWarning)
341341
self.assertRegex(str(w[0].message), 'invalid escape sequence')
342342
self.assertEqual(w[0].filename, '<input>')
343343

Lib/test/test_fstring.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ def test_backslashes_in_string_part(self):
776776
self.assertEqual(f'2\x203', '2 3')
777777
self.assertEqual(f'\x203', ' 3')
778778

779-
with self.assertWarns(DeprecationWarning): # invalid escape sequence
779+
with self.assertWarns(SyntaxWarning): # invalid escape sequence
780780
value = eval(r"f'\{6*7}'")
781781
self.assertEqual(value, '\\42')
782782
self.assertEqual(f'\\{6*7}', '\\42')

Lib/test/test_string_literals.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -109,19 +109,19 @@ def test_eval_str_invalid_escape(self):
109109
for b in range(1, 128):
110110
if b in b"""\n\r"'01234567NU\\abfnrtuvx""":
111111
continue
112-
with self.assertWarns(DeprecationWarning):
112+
with self.assertWarns(SyntaxWarning):
113113
self.assertEqual(eval(r"'\%c'" % b), '\\' + chr(b))
114114

115115
with warnings.catch_warnings(record=True) as w:
116-
warnings.simplefilter('always', category=DeprecationWarning)
116+
warnings.simplefilter('always', category=SyntaxWarning)
117117
eval("'''\n\\z'''")
118118
self.assertEqual(len(w), 1)
119119
self.assertEqual(str(w[0].message), r"invalid escape sequence '\z'")
120120
self.assertEqual(w[0].filename, '<string>')
121121
self.assertEqual(w[0].lineno, 1)
122122

123123
with warnings.catch_warnings(record=True) as w:
124-
warnings.simplefilter('error', category=DeprecationWarning)
124+
warnings.simplefilter('error', category=SyntaxWarning)
125125
with self.assertRaises(SyntaxError) as cm:
126126
eval("'''\n\\z'''")
127127
exc = cm.exception
@@ -133,11 +133,11 @@ def test_eval_str_invalid_escape(self):
133133

134134
def test_eval_str_invalid_octal_escape(self):
135135
for i in range(0o400, 0o1000):
136-
with self.assertWarns(DeprecationWarning):
136+
with self.assertWarns(SyntaxWarning):
137137
self.assertEqual(eval(r"'\%o'" % i), chr(i))
138138

139139
with warnings.catch_warnings(record=True) as w:
140-
warnings.simplefilter('always', category=DeprecationWarning)
140+
warnings.simplefilter('always', category=SyntaxWarning)
141141
eval("'''\n\\407'''")
142142
self.assertEqual(len(w), 1)
143143
self.assertEqual(str(w[0].message),
@@ -146,7 +146,7 @@ def test_eval_str_invalid_octal_escape(self):
146146
self.assertEqual(w[0].lineno, 1)
147147

148148
with warnings.catch_warnings(record=True) as w:
149-
warnings.simplefilter('error', category=DeprecationWarning)
149+
warnings.simplefilter('error', category=SyntaxWarning)
150150
with self.assertRaises(SyntaxError) as cm:
151151
eval("'''\n\\407'''")
152152
exc = cm.exception
@@ -186,19 +186,19 @@ def test_eval_bytes_invalid_escape(self):
186186
for b in range(1, 128):
187187
if b in b"""\n\r"'01234567\\abfnrtvx""":
188188
continue
189-
with self.assertWarns(DeprecationWarning):
189+
with self.assertWarns(SyntaxWarning):
190190
self.assertEqual(eval(r"b'\%c'" % b), b'\\' + bytes([b]))
191191

192192
with warnings.catch_warnings(record=True) as w:
193-
warnings.simplefilter('always', category=DeprecationWarning)
193+
warnings.simplefilter('always', category=SyntaxWarning)
194194
eval("b'''\n\\z'''")
195195
self.assertEqual(len(w), 1)
196196
self.assertEqual(str(w[0].message), r"invalid escape sequence '\z'")
197197
self.assertEqual(w[0].filename, '<string>')
198198
self.assertEqual(w[0].lineno, 1)
199199

200200
with warnings.catch_warnings(record=True) as w:
201-
warnings.simplefilter('error', category=DeprecationWarning)
201+
warnings.simplefilter('error', category=SyntaxWarning)
202202
with self.assertRaises(SyntaxError) as cm:
203203
eval("b'''\n\\z'''")
204204
exc = cm.exception
@@ -209,11 +209,11 @@ def test_eval_bytes_invalid_escape(self):
209209

210210
def test_eval_bytes_invalid_octal_escape(self):
211211
for i in range(0o400, 0o1000):
212-
with self.assertWarns(DeprecationWarning):
212+
with self.assertWarns(SyntaxWarning):
213213
self.assertEqual(eval(r"b'\%o'" % i), bytes([i & 0o377]))
214214

215215
with warnings.catch_warnings(record=True) as w:
216-
warnings.simplefilter('always', category=DeprecationWarning)
216+
warnings.simplefilter('always', category=SyntaxWarning)
217217
eval("b'''\n\\407'''")
218218
self.assertEqual(len(w), 1)
219219
self.assertEqual(str(w[0].message),
@@ -222,7 +222,7 @@ def test_eval_bytes_invalid_octal_escape(self):
222222
self.assertEqual(w[0].lineno, 1)
223223

224224
with warnings.catch_warnings(record=True) as w:
225-
warnings.simplefilter('error', category=DeprecationWarning)
225+
warnings.simplefilter('error', category=SyntaxWarning)
226226
with self.assertRaises(SyntaxError) as cm:
227227
eval("b'''\n\\407'''")
228228
exc = cm.exception
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
A backslash-character pair that is not a valid escape sequence now generates a
2+
:exc:`SyntaxWarning`, instead of :exc:`DeprecationWarning`. For example,
3+
``re.compile("\d+\.\d+")`` now emits a :exc:`SyntaxWarning` (``"\d"`` is an
4+
invalid escape sequence), use raw strings for regular expression:
5+
``re.compile(r"\d+\.\d+")``. In a future Python version, :exc:`SyntaxError`
6+
will eventually be raised, instead of :exc:`SyntaxWarning`. Patch by Victor
7+
Stinner.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Octal escapes with value larger than ``0o377`` (ex: ``"\477"``), deprecated
2+
in Python 3.11, now produce a :exc:`SyntaxWarning`, instead of
3+
:exc:`DeprecationWarning`. In a future Python version they will be
4+
eventually a :exc:`SyntaxError`. Patch by Victor Stinner.

Parser/string_parser.c

+9-2
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,16 @@ warn_invalid_escape_sequence(Parser *p, const char *first_invalid_escape, Token
2121
if (msg == NULL) {
2222
return -1;
2323
}
24-
if (PyErr_WarnExplicitObject(PyExc_DeprecationWarning, msg, p->tok->filename,
24+
PyObject *category;
25+
if (p->feature_version >= 12) {
26+
category = PyExc_SyntaxWarning;
27+
}
28+
else {
29+
category = PyExc_DeprecationWarning;
30+
}
31+
if (PyErr_WarnExplicitObject(category, msg, p->tok->filename,
2532
t->lineno, NULL, NULL) < 0) {
26-
if (PyErr_ExceptionMatches(PyExc_DeprecationWarning)) {
33+
if (PyErr_ExceptionMatches(category)) {
2734
/* Replace the DeprecationWarning exception with a SyntaxError
2835
to get a more accurate error report */
2936
PyErr_Clear();

Tools/c-analyzer/c_parser/_state_machine.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def parse(srclines):
9696
# # end matched parens
9797
# ''')
9898

99-
'''
99+
r'''
100100
# for loop
101101
(?:
102102
\s* \b for

Tools/wasm/wasm_build.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def read_python_version(configure: pathlib.Path = CONFIGURE) -> str:
137137
configure and configure.ac are the canonical source for major and
138138
minor version number.
139139
"""
140-
version_re = re.compile("^PACKAGE_VERSION='(\d\.\d+)'")
140+
version_re = re.compile(r"^PACKAGE_VERSION='(\d\.\d+)'")
141141
with configure.open(encoding="utf-8") as f:
142142
for line in f:
143143
mo = version_re.match(line)

0 commit comments

Comments
 (0)