Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Lib/test/test_fstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,24 @@ def test_misformed_unicode_character_name(self):
r"'\N{GREEK CAPITAL LETTER DELTA'",
])

def test_invalid_escape_location(self):
cases = [
(r'f"\xZZ"', (1, 3, 1, 7)),
(r'f"{x}\xZZ"', (1, 6, 1, 10)),
(r'f"a{x}b\N{NOPE}{y}"', (1, 7, 1, 16)),
(r'f"ok" f"\xZZ"', (1, 9, 1, 13)),
('f"""\\xZZ\nabc"""', (1, 5, 2, 4)),
('f"""abc\ndef\\xZZ"""', (1, 5, 2, 8)),
('x = (1,\n f"\\xZZ")', (2, 5, 2, 9)),
]
for source, location in cases:
with self.subTest(source=source):
with self.assertRaisesRegex(SyntaxError, r'\(unicode error\)') as cm:
compile(source, '<string>', 'exec')
error = cm.exception
self.assertEqual((error.lineno, error.offset,
error.end_lineno, error.end_offset), location)

def test_backslashes_in_expression_part(self):
self.assertEqual(f"{(
1 +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix the location reported for invalid escape sequences in f-strings and
t-strings. The :exc:`SyntaxError` now points at the literal part that
contains the escape, instead of at the closing quote.
2 changes: 1 addition & 1 deletion Parser/action_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -1373,7 +1373,7 @@ _PyPegen_decode_fstring_part(Parser* p, int is_raw, expr_ty constant, Token* tok
is_raw = is_raw || strchr(bstr, '\\') == NULL;
PyObject *str = _PyPegen_decode_string(p, is_raw, bstr, len, token);
if (str == NULL) {
_Pypegen_raise_decode_error(p);
_Pypegen_raise_decode_error_known_location(p, constant);
return NULL;
}
if (_PyArena_AddPyObject(p->arena, str) < 0) {
Expand Down
1 change: 1 addition & 0 deletions Parser/pegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ typedef enum {

// Error handling functions and APIs
int _Pypegen_raise_decode_error(Parser *p);
int _Pypegen_raise_decode_error_known_location(Parser *p, expr_ty a);
int _Pypegen_tokenizer_error(Parser *p);
void *_PyPegen_raise_incomplete_input(Parser *p);
void *_PyPegen_raise_error(Parser *p, PyObject *errtype, int use_mark, const char *errmsg, ...);
Expand Down
20 changes: 18 additions & 2 deletions Parser/pegen_errors.c
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ _Pypegen_tokenizer_error(Parser *p)

int
_Pypegen_raise_decode_error(Parser *p)
{
return _Pypegen_raise_decode_error_known_location(p, NULL);
}

int
_Pypegen_raise_decode_error_known_location(Parser *p, expr_ty a)
{
assert(PyErr_Occurred());
const char *errtype = NULL;
Expand All @@ -120,12 +126,22 @@ _Pypegen_raise_decode_error(Parser *p)
PyErr_Fetch(&type, &value, &tback);
errstr = PyObject_Str(value);
if (errstr) {
RAISE_SYNTAX_ERROR("(%s) %U", errtype, errstr);
if (a != NULL) {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "(%s) %U", errtype, errstr);
}
else {
RAISE_SYNTAX_ERROR("(%s) %U", errtype, errstr);
}
Py_DECREF(errstr);
}
else {
PyErr_Clear();
RAISE_SYNTAX_ERROR("(%s) unknown error", errtype);
if (a != NULL) {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "(%s) unknown error", errtype);
}
else {
RAISE_SYNTAX_ERROR("(%s) unknown error", errtype);
}
}
Py_XDECREF(type);
Py_XDECREF(value);
Expand Down
Loading