Skip to content

Commit 8986092

Browse files
committed
Improved invalid escape sequence warning message
1 parent cfeaa99 commit 8986092

File tree

8 files changed

+78
-29
lines changed

8 files changed

+78
-29
lines changed

Lib/test/test_cmd_line_script.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,9 @@ def test_syntaxerror_invalid_escape_sequence_multi_line(self):
659659
stderr.splitlines()[-3:],
660660
[ b' foo = """\\q"""',
661661
b' ^^^^^^^^',
662-
b'SyntaxError: invalid escape sequence \'\\q\''
662+
b'SyntaxError: "\\q" is an invalid escape sequence. '
663+
b'Such sequences will not work in the future. '
664+
b'Did you mean "\\\\q"? A raw string is also an option.'
663665
],
664666
)
665667

Lib/test/test_codeop.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ def test_warning(self):
282282
# Test that the warning is only returned once.
283283
with warnings_helper.check_warnings(
284284
('"is" with \'str\' literal', SyntaxWarning),
285-
("invalid escape sequence", SyntaxWarning),
285+
('"\\\\e" is an invalid escape sequence', SyntaxWarning),
286286
) as w:
287287
compile_command(r"'\e' is 0")
288288
self.assertEqual(len(w.warnings), 2)

Lib/test/test_string_literals.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ def test_eval_str_invalid_escape(self):
116116
warnings.simplefilter('always', category=SyntaxWarning)
117117
eval("'''\n\\z'''")
118118
self.assertEqual(len(w), 1)
119-
self.assertEqual(str(w[0].message), r"invalid escape sequence '\z'")
119+
self.assertEqual(str(w[0].message), r'"\z" is an invalid escape sequence. '
120+
r'Such sequences will not work in the future. '
121+
r'Did you mean "\\z"? A raw string is also an option.')
120122
self.assertEqual(w[0].filename, '<string>')
121123
self.assertEqual(w[0].lineno, 1)
122124

@@ -126,7 +128,9 @@ def test_eval_str_invalid_escape(self):
126128
eval("'''\n\\z'''")
127129
exc = cm.exception
128130
self.assertEqual(w, [])
129-
self.assertEqual(exc.msg, r"invalid escape sequence '\z'")
131+
self.assertEqual(exc.msg, r'"\z" is an invalid escape sequence. '
132+
r'Such sequences will not work in the future. '
133+
r'Did you mean "\\z"? A raw string is also an option.')
130134
self.assertEqual(exc.filename, '<string>')
131135
self.assertEqual(exc.lineno, 1)
132136
self.assertEqual(exc.offset, 1)
@@ -153,7 +157,9 @@ def test_eval_str_invalid_octal_escape(self):
153157
eval("'''\n\\407'''")
154158
self.assertEqual(len(w), 1)
155159
self.assertEqual(str(w[0].message),
156-
r"invalid octal escape sequence '\407'")
160+
r'"\407" is an invalid octal escape sequence. '
161+
r'Such sequences will not work in the future. '
162+
r'Did you mean "\\407"? A raw string is also an option.')
157163
self.assertEqual(w[0].filename, '<string>')
158164
self.assertEqual(w[0].lineno, 1)
159165

@@ -163,7 +169,9 @@ def test_eval_str_invalid_octal_escape(self):
163169
eval("'''\n\\407'''")
164170
exc = cm.exception
165171
self.assertEqual(w, [])
166-
self.assertEqual(exc.msg, r"invalid octal escape sequence '\407'")
172+
self.assertEqual(exc.msg, r'"\407" is an invalid octal escape sequence. '
173+
r'Such sequences will not work in the future. '
174+
r'Did you mean "\\407"? A raw string is also an option.')
167175
self.assertEqual(exc.filename, '<string>')
168176
self.assertEqual(exc.lineno, 1)
169177
self.assertEqual(exc.offset, 1)
@@ -205,7 +213,9 @@ def test_eval_bytes_invalid_escape(self):
205213
warnings.simplefilter('always', category=SyntaxWarning)
206214
eval("b'''\n\\z'''")
207215
self.assertEqual(len(w), 1)
208-
self.assertEqual(str(w[0].message), r"invalid escape sequence '\z'")
216+
self.assertEqual(str(w[0].message), r'"\z" is an invalid escape sequence. '
217+
r'Such sequences will not work in the future. '
218+
r'Did you mean "\\z"? A raw string is also an option.')
209219
self.assertEqual(w[0].filename, '<string>')
210220
self.assertEqual(w[0].lineno, 1)
211221

@@ -215,7 +225,9 @@ def test_eval_bytes_invalid_escape(self):
215225
eval("b'''\n\\z'''")
216226
exc = cm.exception
217227
self.assertEqual(w, [])
218-
self.assertEqual(exc.msg, r"invalid escape sequence '\z'")
228+
self.assertEqual(exc.msg, r'"\z" is an invalid escape sequence. '
229+
r'Such sequences will not work in the future. '
230+
r'Did you mean "\\z"? A raw string is also an option.')
219231
self.assertEqual(exc.filename, '<string>')
220232
self.assertEqual(exc.lineno, 1)
221233

@@ -228,8 +240,9 @@ def test_eval_bytes_invalid_octal_escape(self):
228240
warnings.simplefilter('always', category=SyntaxWarning)
229241
eval("b'''\n\\407'''")
230242
self.assertEqual(len(w), 1)
231-
self.assertEqual(str(w[0].message),
232-
r"invalid octal escape sequence '\407'")
243+
self.assertEqual(str(w[0].message), r'"\407" is an invalid octal escape sequence. '
244+
r'Such sequences will not work in the future. '
245+
r'Did you mean "\\407"? A raw string is also an option.')
233246
self.assertEqual(w[0].filename, '<string>')
234247
self.assertEqual(w[0].lineno, 1)
235248

@@ -239,7 +252,9 @@ def test_eval_bytes_invalid_octal_escape(self):
239252
eval("b'''\n\\407'''")
240253
exc = cm.exception
241254
self.assertEqual(w, [])
242-
self.assertEqual(exc.msg, r"invalid octal escape sequence '\407'")
255+
self.assertEqual(exc.msg, r'"\407" is an invalid octal escape sequence. '
256+
r'Such sequences will not work in the future. '
257+
r'Did you mean "\\407"? A raw string is also an option.')
243258
self.assertEqual(exc.filename, '<string>')
244259
self.assertEqual(exc.lineno, 1)
245260

Lib/test/test_unparse.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,9 @@ def test_multiquote_joined_string(self):
651651

652652
def test_backslash_in_format_spec(self):
653653
import re
654-
msg = re.escape("invalid escape sequence '\\ '")
654+
msg = re.escape('"\\ " is an invalid escape sequence. '
655+
'Such sequences will not work in the future. '
656+
'Did you mean "\\\\ "? A raw string is also an option.')
655657
with self.assertWarnsRegex(SyntaxWarning, msg):
656658
self.check_ast_roundtrip("""f"{x:\\ }" """)
657659
self.check_ast_roundtrip("""f"{x:\\n}" """)

Objects/bytesobject.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,25 +1184,28 @@ PyObject *PyBytes_DecodeEscape(const char *s,
11841184
unsigned char c = *first_invalid_escape;
11851185
if ('4' <= c && c <= '7') {
11861186
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
1187-
"invalid octal escape sequence '\\%.3s'",
1188-
first_invalid_escape) < 0)
1187+
"'\\%.3s' is an invalid escape sequence. "
1188+
"Such sequences will not work in the future. "
1189+
"Did you mean \"\\\\%.3s\"? A raw string is also an option.",
1190+
first_invalid_escape, first_invalid_escape) < 0)
11891191
{
11901192
Py_DECREF(result);
11911193
return NULL;
11921194
}
11931195
}
11941196
else {
11951197
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
1196-
"invalid escape sequence '\\%c'",
1197-
c) < 0)
1198+
"'\\%c' is an invalid escape sequence. "
1199+
"Such sequences will not work in the future. "
1200+
"Did you mean \"\\\\%c\"? A raw string is also an option.",
1201+
c, c) < 0)
11981202
{
11991203
Py_DECREF(result);
12001204
return NULL;
12011205
}
12021206
}
12031207
}
12041208
return result;
1205-
12061209
}
12071210
/* -------------------------------------------------------------------- */
12081211
/* object api */

Objects/unicodeobject.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6802,17 +6802,21 @@ _PyUnicode_DecodeUnicodeEscapeStateful(const char *s,
68026802
unsigned char c = *first_invalid_escape;
68036803
if ('4' <= c && c <= '7') {
68046804
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
6805-
"invalid octal escape sequence '\\%.3s'",
6806-
first_invalid_escape) < 0)
6805+
"\"\\%.3s\" is an invalid octal escape sequence. "
6806+
"Such sequences will not work in the future. "
6807+
"Did you mean \"\\\\%.3s\"? A raw string is also an option.",
6808+
first_invalid_escape, first_invalid_escape) < 0)
68076809
{
68086810
Py_DECREF(result);
68096811
return NULL;
68106812
}
68116813
}
68126814
else {
68136815
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
6814-
"invalid escape sequence '\\%c'",
6815-
c) < 0)
6816+
"\"\\%c\" is an invalid escape sequence. "
6817+
"Such sequences will not work in the future. "
6818+
"Did you mean \"\\\\%c\"? A raw string is also an option.",
6819+
c, c) < 0)
68166820
{
68176821
Py_DECREF(result);
68186822
return NULL;

Parser/string_parser.c

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,16 @@ warn_invalid_escape_sequence(Parser *p, const char *first_invalid_escape, Token
2828
int octal = ('4' <= c && c <= '7');
2929
PyObject *msg =
3030
octal
31-
? PyUnicode_FromFormat("invalid octal escape sequence '\\%.3s'",
32-
first_invalid_escape)
33-
: PyUnicode_FromFormat("invalid escape sequence '\\%c'", c);
31+
? PyUnicode_FromFormat(
32+
"\"\\%.3s\" is an invalid octal escape sequence. "
33+
"Such sequences will not work in the future. "
34+
"Did you mean \"\\\\%.3s\"? A raw string is also an option.",
35+
first_invalid_escape, first_invalid_escape)
36+
: PyUnicode_FromFormat(
37+
"\"\\%c\" is an invalid escape sequence. "
38+
"Such sequences will not work in the future. "
39+
"Did you mean \"\\\\%c\"? A raw string is also an option.",
40+
c, c);
3441
if (msg == NULL) {
3542
return -1;
3643
}
@@ -53,11 +60,18 @@ warn_invalid_escape_sequence(Parser *p, const char *first_invalid_escape, Token
5360
error location, if p->known_err_token is not set. */
5461
p->known_err_token = t;
5562
if (octal) {
56-
RAISE_SYNTAX_ERROR("invalid octal escape sequence '\\%.3s'",
57-
first_invalid_escape);
63+
RAISE_SYNTAX_ERROR(
64+
"\"\\%.3s\" is an invalid octal escape sequence. "
65+
"Such sequences will not work in the future. "
66+
"Did you mean \"\\\\%.3s\"? A raw string is also an option.",
67+
first_invalid_escape, first_invalid_escape);
5868
}
5969
else {
60-
RAISE_SYNTAX_ERROR("invalid escape sequence '\\%c'", c);
70+
RAISE_SYNTAX_ERROR(
71+
"\"\\%c\" is an invalid escape sequence. "
72+
"Such sequences will not work in the future. "
73+
"Did you mean \"\\\\%c\"? A raw string is also an option.",
74+
c, c);
6175
}
6276
}
6377
Py_DECREF(msg);

Parser/tokenizer/helpers.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,10 @@ _PyTokenizer_warn_invalid_escape_sequence(struct tok_state *tok, int first_inval
113113
}
114114

115115
PyObject *msg = PyUnicode_FromFormat(
116-
"invalid escape sequence '\\%c'",
116+
"\"\\%c\" is an invalid escape sequence. "
117+
"Such sequences will not work in the future. "
118+
"Did you mean \"\\\\%c\"? A raw string is also an option.",
119+
(char) first_invalid_escape_char,
117120
(char) first_invalid_escape_char
118121
);
119122

@@ -129,7 +132,13 @@ _PyTokenizer_warn_invalid_escape_sequence(struct tok_state *tok, int first_inval
129132
/* Replace the SyntaxWarning exception with a SyntaxError
130133
to get a more accurate error report */
131134
PyErr_Clear();
132-
return _PyTokenizer_syntaxerror(tok, "invalid escape sequence '\\%c'", (char) first_invalid_escape_char);
135+
136+
return _PyTokenizer_syntaxerror(tok,
137+
"\"\\%c\" is an invalid escape sequence. "
138+
"Such sequences will not work in the future. "
139+
"Did you mean \"\\\\%c\"? A raw string is also an option.",
140+
(char) first_invalid_escape_char,
141+
(char) first_invalid_escape_char);
133142
}
134143

135144
return -1;

0 commit comments

Comments
 (0)