Skip to content

Commit

Permalink
pythongh-105194: Fix format specifier escaped characters in f-strings
Browse files Browse the repository at this point in the history
  • Loading branch information
pablogsal committed Jun 2, 2023
1 parent 0430e97 commit a63b87a
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ fstring_conversion[ResultTokenWithMetadata*]:
fstring_full_format_spec[ResultTokenWithMetadata*]:
| colon=':' spec=fstring_format_spec* { _PyPegen_setup_full_format_spec(p, colon, (asdl_expr_seq *) spec, EXTRA) }
fstring_format_spec[expr_ty]:
| t=FSTRING_MIDDLE { _PyPegen_constant_from_token(p, t) }
| t=FSTRING_MIDDLE { _PyPegen_decoded_constant_from_token(p, t) }
| fstring_replacement_field
fstring[expr_ty]:
| a=FSTRING_START b=fstring_middle* c=FSTRING_END { _PyPegen_joined_str(p, a, (asdl_expr_seq*)b, c) }
Expand Down
8 changes: 8 additions & 0 deletions Lib/test/test_fstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,14 @@ def test_format_specifier_expressions(self):
# the : or ! itself.
"""f'{"s"!{"r"}}'""",
])

def test_custom_format_specifier(self):
class CustomFormat:
def __format__(self, format_spec):
return format_spec

self.assertEqual(f'{CustomFormat():\n}', '\n')
self.assertEqual(f'{CustomFormat():\u2603}', '☃')

def test_side_effect_order(self):
class X:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Do not escape with backslashes f-string format specifiers. Patch by Pablo
Galindo
19 changes: 19 additions & 0 deletions Parser/action_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,25 @@ _PyPegen_joined_str(Parser *p, Token* a, asdl_expr_seq* raw_expressions, Token*b
p->arena);
}

expr_ty _PyPegen_decoded_constant_from_token(Parser* p, Token* tok) {
Py_ssize_t bsize;
char* bstr;
if (PyBytes_AsStringAndSize(tok->bytes, &bstr, &bsize) == -1) {
return NULL;
}
PyObject* str = _PyPegen_decode_string(p, 0, bstr, bsize, tok);
if (str == NULL) {
return NULL;
}
if (_PyArena_AddPyObject(p->arena, str) < 0) {
Py_DECREF(str);
return NULL;
}
return _PyAST_Constant(str, NULL, tok->lineno, tok->col_offset,
tok->end_lineno, tok->end_col_offset,
p->arena);
}

expr_ty _PyPegen_constant_from_token(Parser* p, Token* tok) {
char* bstr = PyBytes_AsString(tok->bytes);
if (bstr == NULL) {
Expand Down
2 changes: 1 addition & 1 deletion Parser/parser.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Parser/pegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ expr_ty _PyPegen_collect_call_seqs(Parser *, asdl_expr_seq *, asdl_seq *,
int lineno, int col_offset, int end_lineno,
int end_col_offset, PyArena *arena);
expr_ty _PyPegen_constant_from_token(Parser* p, Token* tok);
expr_ty _PyPegen_decoded_constant_from_token(Parser* p, Token* tok);
expr_ty _PyPegen_constant_from_string(Parser* p, Token* tok);
expr_ty _PyPegen_concatenate_strings(Parser *p, asdl_expr_seq *, int, int, int, int, PyArena *);
expr_ty _PyPegen_FetchRawForm(Parser *p, int, int, int, int);
Expand Down

0 comments on commit a63b87a

Please sign in to comment.