mirror of
https://github.com/python/cpython.git
synced 2026-10-28 10:25:05 +08:00
[3.14] gh-160491: Fix location of invalid escapes in f-strings and t-strings (GH-160493) (#160531)
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.
(cherry picked from commit 2258026edd)
Co-authored-by: Lysandros Nikolaou <lisandrosnik@gmail.com>
This commit is contained in:
1 parent
53002b9221
commit
dfbde8cd5b
5 files changed
+41
-3
No files matched your search
@@ -1046,6 +1046,24 @@ except Exception:
|
||||
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 +
|
||||
|
||||
@@ -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.
|
||||
@@ -1302,7 +1302,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) {
|
||||
|
||||
@@ -175,6 +175,7 @@ typedef enum {
|
||||
} TARGETS_TYPE;
|
||||
|
||||
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_error(Parser *p, PyObject *errtype, int use_mark, const char *errmsg, ...);
|
||||
void *_PyPegen_raise_error_known_location(Parser *p, PyObject *errtype,
|
||||
|
||||
+18
-2
@@ -81,6 +81,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;
|
||||
@@ -98,12 +104,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);
|
||||
|
||||
Reference in new issue
Block a user