Skip to content

Commit f7f8b8b

Browse files
miss-islingtonpablogsalblurb-it[bot]
authored
[3.13] gh-124363: Treat debug expressions in f-string as raw strings (GH-128399) (#129187)
gh-124363: Treat debug expressions in f-string as raw strings (GH-128399) (cherry picked from commit 60a3a0d) Co-authored-by: Pablo Galindo Salgado <[email protected]> Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
1 parent 0b90dc8 commit f7f8b8b

File tree

3 files changed

+45
-43
lines changed

3 files changed

+45
-43
lines changed

Lib/test/test_fstring.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,6 +1650,14 @@ def __repr__(self):
16501650
#self.assertEqual(f'X{x =}Y', 'Xx\t='+repr(x)+'Y')
16511651
#self.assertEqual(f'X{x = }Y', 'Xx\t=\t'+repr(x)+'Y')
16521652

1653+
def test_debug_expressions_are_raw_strings(self):
1654+
1655+
self.assertEqual(f'{b"\N{OX}"=}', 'b"\\N{OX}"=b\'\\\\N{OX}\'')
1656+
self.assertEqual(f'{r"\xff"=}', 'r"\\xff"=\'\\\\xff\'')
1657+
self.assertEqual(f'{r"\n"=}', 'r"\\n"=\'\\\\n\'')
1658+
self.assertEqual(f"{'\''=}", "'\\''=\"'\"")
1659+
self.assertEqual(f'{'\xc5'=}', r"'\xc5'='Å'")
1660+
16531661
def test_walrus(self):
16541662
x = 20
16551663
# This isn't an assignment expression, it's 'x', with a format
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Treat debug expressions in f-string as raw strings. Patch by Pablo Galindo

Parser/action_helpers.c

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -961,8 +961,6 @@ _PyPegen_check_fstring_conversion(Parser *p, Token* conv_token, expr_ty conv)
961961
return result_token_with_metadata(p, conv, conv_token->metadata);
962962
}
963963

964-
static asdl_expr_seq *
965-
unpack_top_level_joined_strs(Parser *p, asdl_expr_seq *raw_expressions);
966964
ResultTokenWithMetadata *
967965
_PyPegen_setup_full_format_spec(Parser *p, Token *colon, asdl_expr_seq *spec, int lineno, int col_offset,
968966
int end_lineno, int end_col_offset, PyArena *arena)
@@ -1271,69 +1269,64 @@ _PyPegen_decode_fstring_part(Parser* p, int is_raw, expr_ty constant, Token* tok
12711269
p->arena);
12721270
}
12731271

1274-
static asdl_expr_seq *
1275-
unpack_top_level_joined_strs(Parser *p, asdl_expr_seq *raw_expressions)
1276-
{
1272+
expr_ty
1273+
_PyPegen_joined_str(Parser *p, Token* a, asdl_expr_seq* expr, Token*b) {
1274+
12771275
/* The parser might put multiple f-string values into an individual
12781276
* JoinedStr node at the top level due to stuff like f-string debugging
12791277
* expressions. This function flattens those and promotes them to the
12801278
* upper level. Only simplifies AST, but the compiler already takes care
12811279
* of the regular output, so this is not necessary if you are not going
12821280
* to expose the output AST to Python level. */
12831281

1284-
Py_ssize_t i, req_size, raw_size;
1285-
1286-
req_size = raw_size = asdl_seq_LEN(raw_expressions);
1287-
expr_ty expr;
1288-
for (i = 0; i < raw_size; i++) {
1289-
expr = asdl_seq_GET(raw_expressions, i);
1290-
if (expr->kind == JoinedStr_kind) {
1291-
req_size += asdl_seq_LEN(expr->v.JoinedStr.values) - 1;
1292-
}
1293-
}
1294-
1295-
asdl_expr_seq *expressions = _Py_asdl_expr_seq_new(req_size, p->arena);
1296-
if (expressions == NULL) {
1297-
return NULL;
1298-
}
1299-
1300-
Py_ssize_t raw_index, req_index = 0;
1301-
for (raw_index = 0; raw_index < raw_size; raw_index++) {
1302-
expr = asdl_seq_GET(raw_expressions, raw_index);
1303-
if (expr->kind == JoinedStr_kind) {
1304-
asdl_expr_seq *values = expr->v.JoinedStr.values;
1305-
for (Py_ssize_t n = 0; n < asdl_seq_LEN(values); n++) {
1306-
asdl_seq_SET(expressions, req_index, asdl_seq_GET(values, n));
1307-
req_index++;
1308-
}
1309-
} else {
1310-
asdl_seq_SET(expressions, req_index, expr);
1311-
req_index++;
1282+
Py_ssize_t n_items = asdl_seq_LEN(expr);
1283+
Py_ssize_t total_items = n_items;
1284+
for (Py_ssize_t i = 0; i < n_items; i++) {
1285+
expr_ty item = asdl_seq_GET(expr, i);
1286+
if (item->kind == JoinedStr_kind) {
1287+
total_items += asdl_seq_LEN(item->v.JoinedStr.values) - 1;
13121288
}
13131289
}
1314-
return expressions;
1315-
}
1316-
1317-
expr_ty
1318-
_PyPegen_joined_str(Parser *p, Token* a, asdl_expr_seq* raw_expressions, Token*b) {
1319-
1320-
asdl_expr_seq *expr = unpack_top_level_joined_strs(p, raw_expressions);
1321-
Py_ssize_t n_items = asdl_seq_LEN(expr);
13221290

13231291
const char* quote_str = PyBytes_AsString(a->bytes);
13241292
if (quote_str == NULL) {
13251293
return NULL;
13261294
}
13271295
int is_raw = strpbrk(quote_str, "rR") != NULL;
13281296

1329-
asdl_expr_seq *seq = _Py_asdl_expr_seq_new(n_items, p->arena);
1297+
asdl_expr_seq *seq = _Py_asdl_expr_seq_new(total_items, p->arena);
13301298
if (seq == NULL) {
13311299
return NULL;
13321300
}
13331301

13341302
Py_ssize_t index = 0;
13351303
for (Py_ssize_t i = 0; i < n_items; i++) {
13361304
expr_ty item = asdl_seq_GET(expr, i);
1305+
1306+
// This should correspond to a JoinedStr node of two elements
1307+
// created _PyPegen_formatted_value. This situation can only be the result of
1308+
// a f-string debug expression where the first element is a constant with the text and the second
1309+
// a formatted value with the expression.
1310+
if (item->kind == JoinedStr_kind) {
1311+
asdl_expr_seq *values = item->v.JoinedStr.values;
1312+
if (asdl_seq_LEN(values) != 2) {
1313+
PyErr_Format(PyExc_SystemError,
1314+
"unexpected JoinedStr node without debug data in f-string at line %d",
1315+
item->lineno);
1316+
return NULL;
1317+
}
1318+
1319+
expr_ty first = asdl_seq_GET(values, 0);
1320+
assert(first->kind == Constant_kind);
1321+
asdl_seq_SET(seq, index++, first);
1322+
1323+
expr_ty second = asdl_seq_GET(values, 1);
1324+
assert(second->kind == FormattedValue_kind);
1325+
asdl_seq_SET(seq, index++, second);
1326+
1327+
continue;
1328+
}
1329+
13371330
if (item->kind == Constant_kind) {
13381331
item = _PyPegen_decode_fstring_part(p, is_raw, item, b);
13391332
if (item == NULL) {
@@ -1352,7 +1345,7 @@ _PyPegen_joined_str(Parser *p, Token* a, asdl_expr_seq* raw_expressions, Token*b
13521345
}
13531346

13541347
asdl_expr_seq *resized_exprs;
1355-
if (index != n_items) {
1348+
if (index != total_items) {
13561349
resized_exprs = _Py_asdl_expr_seq_new(index, p->arena);
13571350
if (resized_exprs == NULL) {
13581351
return NULL;

0 commit comments

Comments
 (0)