Skip to content

Commit 103ae4e

Browse files
[3.12] gh-105194: Fix format specifier escaped characters in f-strings (GH-105231) (#105234)
1 parent 46cc4f0 commit 103ae4e

File tree

6 files changed

+34
-2
lines changed

6 files changed

+34
-2
lines changed

Grammar/python.gram

+1-1
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,7 @@ fstring_conversion[ResultTokenWithMetadata*]:
923923
fstring_full_format_spec[ResultTokenWithMetadata*]:
924924
| colon=':' spec=fstring_format_spec* { _PyPegen_setup_full_format_spec(p, colon, (asdl_expr_seq *) spec, EXTRA) }
925925
fstring_format_spec[expr_ty]:
926-
| t=FSTRING_MIDDLE { _PyPegen_constant_from_token(p, t) }
926+
| t=FSTRING_MIDDLE { _PyPegen_decoded_constant_from_token(p, t) }
927927
| fstring_replacement_field
928928
fstring[expr_ty]:
929929
| a=FSTRING_START b=fstring_middle* c=FSTRING_END { _PyPegen_joined_str(p, a, (asdl_expr_seq*)b, c) }

Lib/test/test_fstring.py

+10
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,16 @@ def test_format_specifier_expressions(self):
764764
"""f'{"s"!{"r"}}'""",
765765
])
766766

767+
def test_custom_format_specifier(self):
768+
class CustomFormat:
769+
def __format__(self, format_spec):
770+
return format_spec
771+
772+
self.assertEqual(f'{CustomFormat():\n}', '\n')
773+
self.assertEqual(f'{CustomFormat():\u2603}', '☃')
774+
with self.assertWarns(SyntaxWarning):
775+
exec('f"{F():¯\_(ツ)_/¯}"', {'F': CustomFormat})
776+
767777
def test_side_effect_order(self):
768778
class X:
769779
def __init__(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Do not escape with backslashes f-string format specifiers. Patch by Pablo
2+
Galindo

Parser/action_helpers.c

+19
Original file line numberDiff line numberDiff line change
@@ -1356,6 +1356,25 @@ _PyPegen_joined_str(Parser *p, Token* a, asdl_expr_seq* raw_expressions, Token*b
13561356
p->arena);
13571357
}
13581358

1359+
expr_ty _PyPegen_decoded_constant_from_token(Parser* p, Token* tok) {
1360+
Py_ssize_t bsize;
1361+
char* bstr;
1362+
if (PyBytes_AsStringAndSize(tok->bytes, &bstr, &bsize) == -1) {
1363+
return NULL;
1364+
}
1365+
PyObject* str = _PyPegen_decode_string(p, 0, bstr, bsize, tok);
1366+
if (str == NULL) {
1367+
return NULL;
1368+
}
1369+
if (_PyArena_AddPyObject(p->arena, str) < 0) {
1370+
Py_DECREF(str);
1371+
return NULL;
1372+
}
1373+
return _PyAST_Constant(str, NULL, tok->lineno, tok->col_offset,
1374+
tok->end_lineno, tok->end_col_offset,
1375+
p->arena);
1376+
}
1377+
13591378
expr_ty _PyPegen_constant_from_token(Parser* p, Token* tok) {
13601379
char* bstr = PyBytes_AsString(tok->bytes);
13611380
if (bstr == NULL) {

Parser/parser.c

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Parser/pegen.h

+1
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ expr_ty _PyPegen_collect_call_seqs(Parser *, asdl_expr_seq *, asdl_seq *,
328328
int lineno, int col_offset, int end_lineno,
329329
int end_col_offset, PyArena *arena);
330330
expr_ty _PyPegen_constant_from_token(Parser* p, Token* tok);
331+
expr_ty _PyPegen_decoded_constant_from_token(Parser* p, Token* tok);
331332
expr_ty _PyPegen_constant_from_string(Parser* p, Token* tok);
332333
expr_ty _PyPegen_concatenate_strings(Parser *p, asdl_expr_seq *, int, int, int, int, PyArena *);
333334
expr_ty _PyPegen_FetchRawForm(Parser *p, int, int, int, int);

0 commit comments

Comments
 (0)