Skip to content

Commit 2258026

Browse files
authored
gh-160491: Fix location of invalid escapes in f-strings and t-strings (#160493)
Literal parts of f-strings and t-strings are decoded only after the whole string has been parsed, so a decode error took the location of the last token. The resulting SyntaxError pointed at the closing quote, and for multi-line strings it could be on the wrong line. Raise the error at the literal part being decoded instead.
1 parent 321925c commit 2258026

5 files changed

Lines changed: 41 additions & 3 deletions

File tree

‎Lib/test/test_fstring.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,24 @@ def test_misformed_unicode_character_name(self):
10961096
r"'\N{GREEK CAPITAL LETTER DELTA'",
10971097
])
10981098

1099+
def test_invalid_escape_location(self):
1100+
cases = [
1101+
(r'f"\xZZ"', (1, 3, 1, 7)),
1102+
(r'f"{x}\xZZ"', (1, 6, 1, 10)),
1103+
(r'f"a{x}b\N{NOPE}{y}"', (1, 7, 1, 16)),
1104+
(r'f"ok" f"\xZZ"', (1, 9, 1, 13)),
1105+
('f"""\\xZZ\nabc"""', (1, 5, 2, 4)),
1106+
('f"""abc\ndef\\xZZ"""', (1, 5, 2, 8)),
1107+
('x = (1,\n f"\\xZZ")', (2, 5, 2, 9)),
1108+
]
1109+
for source, location in cases:
1110+
with self.subTest(source=source):
1111+
with self.assertRaisesRegex(SyntaxError, r'\(unicode error\)') as cm:
1112+
compile(source, '<string>', 'exec')
1113+
error = cm.exception
1114+
self.assertEqual((error.lineno, error.offset,
1115+
error.end_lineno, error.end_offset), location)
1116+
10991117
def test_backslashes_in_expression_part(self):
11001118
self.assertEqual(f"{(
11011119
1 +
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix the location reported for invalid escape sequences in f-strings and
2+
t-strings. The :exc:`SyntaxError` now points at the literal part that
3+
contains the escape, instead of at the closing quote.

‎Parser/action_helpers.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1373,7 +1373,7 @@ _PyPegen_decode_fstring_part(Parser* p, int is_raw, expr_ty constant, Token* tok
13731373
is_raw = is_raw || strchr(bstr, '\\') == NULL;
13741374
PyObject *str = _PyPegen_decode_string(p, is_raw, bstr, len, token);
13751375
if (str == NULL) {
1376-
_Pypegen_raise_decode_error(p);
1376+
_Pypegen_raise_decode_error_known_location(p, constant);
13771377
return NULL;
13781378
}
13791379
if (_PyArena_AddPyObject(p->arena, str) < 0) {

‎Parser/pegen.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ typedef enum {
214214

215215
// Error handling functions and APIs
216216
int _Pypegen_raise_decode_error(Parser *p);
217+
int _Pypegen_raise_decode_error_known_location(Parser *p, expr_ty a);
217218
int _Pypegen_tokenizer_error(Parser *p);
218219
void *_PyPegen_raise_incomplete_input(Parser *p);
219220
void *_PyPegen_raise_error(Parser *p, PyObject *errtype, int use_mark, const char *errmsg, ...);

‎Parser/pegen_errors.c‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ _Pypegen_tokenizer_error(Parser *p)
103103

104104
int
105105
_Pypegen_raise_decode_error(Parser *p)
106+
{
107+
return _Pypegen_raise_decode_error_known_location(p, NULL);
108+
}
109+
110+
int
111+
_Pypegen_raise_decode_error_known_location(Parser *p, expr_ty a)
106112
{
107113
assert(PyErr_Occurred());
108114
const char *errtype = NULL;
@@ -120,12 +126,22 @@ _Pypegen_raise_decode_error(Parser *p)
120126
PyErr_Fetch(&type, &value, &tback);
121127
errstr = PyObject_Str(value);
122128
if (errstr) {
123-
RAISE_SYNTAX_ERROR("(%s) %U", errtype, errstr);
129+
if (a != NULL) {
130+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "(%s) %U", errtype, errstr);
131+
}
132+
else {
133+
RAISE_SYNTAX_ERROR("(%s) %U", errtype, errstr);
134+
}
124135
Py_DECREF(errstr);
125136
}
126137
else {
127138
PyErr_Clear();
128-
RAISE_SYNTAX_ERROR("(%s) unknown error", errtype);
139+
if (a != NULL) {
140+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "(%s) unknown error", errtype);
141+
}
142+
else {
143+
RAISE_SYNTAX_ERROR("(%s) unknown error", errtype);
144+
}
129145
}
130146
Py_XDECREF(type);
131147
Py_XDECREF(value);

0 commit comments

Comments
 (0)