10000 Fix unparsing implicit concat of t-strings with f-strings · python/cpython@53fc995 · GitHub
[go: up one dir, main page]

Skip to content

Commit 53fc995

Browse files
committed
Fix unparsing implicit concat of t-strings with f-strings
1 parent 2282ccc commit 53fc995

File tree

4 files changed

+112
-9
lines changed

4 files changed

+112
-9
lines changed

Lib/_ast_unparse.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ def _write_str_avoiding_backslashes(self, string, *, quote_types=_ALL_QUOTES):
573573
quote_type = quote_types[0]
574574
self.write(f"{quote_type}{string}{quote_type}")
575575

576-
def _ftstring_helper(self, node, ftstring_parts):
576+
def _ftstring_helper(self, ftstring_parts):
577577
new_ftstring_parts = []
578578
quote_types = list(_ALL_QUOTES)
579579
fallback_to_repr = False
@@ -615,22 +615,43 @@ def _ftstring_helper(self, node, ftstring_parts):
615615
quote_type = quote_types[0]
616616
self.write(f"{quote_type}{value}{quote_type}")
617617

618-
def _write_ftstring(self, node, prefix):
618+
def _write_ftstring(self, values, prefix):
619619
self.write(prefix)
620620
fstring_parts = []
621-
for value in node.values:
621+
for value in values:
622622
with self.buffered() as buffer:
623623
self._write_ftstring_inner(value)
624624
fstring_parts.append(
625625
("".join(buffer), isinstance(value, Constant))
626626
)
627-
self._ftstring_helper(node, fstring_parts)
627+
self._ftstring_helper(fstring_parts)
628+
629+
def _tstring_helper(self, node):
630+
last_idx = 0
631+
for i, value in enumerate(node.values):
632+
# This can happen if we have an implicit concat of a t-string
633+
# with an f-string
634+
if isinstance(value, FormattedValue):
635+
if i > last_idx:
636+
# Write t-string until here
637+
self._write_ftstring(node.values[last_idx:i], "t")
638+
self.write(" ")
639+
# Write f-string with the current formatted value
640+
self._write_ftstring([node.values[i]], "f")
641+
if i + 1 < len(node.values):
642+
# Only add a space if there are more values after this
643+
self.write(" ")
644+
last_idx = i + 1
645+
646+
if last_idx < len(node.values):
647+
# Write t-string from last_idx to end
648+
self._write_ftstring(node.values[last_idx:], "t")
628649

629650
def visit_JoinedStr(self, node):
630-
self._write_ftstring(node, "f")
651+
self._write_ftstring(node.values, "f")
631652

632653
def visit_TemplateStr(self, node):
633-
self._write_ftstring(node, "t")
654+
self._tstring_helper(node)
634655

635656
def _write_ftstring_inner(self, node, is_format_spec=False):
636657
if isinstance(node, JoinedStr):

Lib/test/support/ast_helper.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ def traverse_compare(a, b, missing=object()):
1616
self.fail(f"{type(a)!r} is not {type(b)!r}")
1717
if isinstance(a, ast.AST):
1818
for field in a._fields:
19+
if isinstance(a, ast.Constant) and field == "kind":
20+
# Skip the 'kind' field for ast.Constant
21+
continue
1922
value1 = getattr(a, field, missing)
2023
value2 = getattr(b, field, missing)
2124
# Singletons are equal by definition, so further

Lib/test/test_unparse.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,15 @@ def test_fstrings_pep701(self):
202202
self.check_ast_roundtrip('f" something { my_dict["key"] } something else "')
203203
self.check_ast_roundtrip('f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"')
204204

205+
def test_tstrings(self):
206+
self.check_ast_roundtrip("t'foo'")
207+
self.check_ast_roundtrip("t'foo {bar}'")
208+
self.check_ast_roundtrip("t'foo {bar!s:.2f}'")
209+
self.check_ast_roundtrip("t'foo {bar}' f'{bar}'")
210+
self.check_ast_roundtrip("f'{bar}' t'foo {bar}'")
211+
self.check_ast_roundtrip("t'foo {bar}' fr'\\hello {bar}'")
212+
self.check_ast_roundtrip("t'foo {bar}' u'bar'")
213+
205214
def test_strings(self):
206215
self.check_ast_roundtrip("u'foo'")
207216
self.check_ast_roundtrip("r'foo'")

Python/ast_unparse.c

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -663,15 +663,27 @@ build_ftstring_body(asdl_expr_seq *values, bool is_format_spec)
663663
}
664664

665665
static int
666-
append_templatestr(PyUnicodeWriter *writer, expr_ty e)
666+
_write_values_subarray(PyUnicodeWriter *writer, asdl_expr_seq *values, Py_ssize_t first_idx,
667+
Py_ssize_t last_idx, char prefix, PyArena *arena)
667668
{
668669
int result = -1;
669-
PyObject *body = build_ftstring_body(e->v.TemplateStr.values, 0);
670+
671+
asdl_expr_seq *new_values = _Py_asdl_expr_seq_new(last_idx - first_idx + 1, arena);
672+
if (!new_values) {
673+
return result;
674+
}
675+
676+
Py_ssize_t j = 0;
677+
for (Py_ssize_t i = first_idx; i <= last_idx; ++i) {
678+
asdl_seq_SET(new_values, j++, asdl_seq_GET(values, i));
679+
}
680+
681+
PyObject *body = build_ftstring_body(new_values, false);
670682
if (!body) {
671683
return result;
672684
}
673685

674-
if (-1 != append_charp(writer, "t") &&
686+
if (-1 != append_char(writer, prefix) &&
675687
-1 != append_repr(writer, body))
676688
{
677689
result = 0;
@@ -680,6 +692,64 @@ append_templatestr(PyUnicodeWriter *writer, expr_ty e)
680692
return result;
681693
}
682694

695+
static int
696+
append_templatestr(PyUnicodeWriter *writer, expr_ty e)
697+
{
698+
PyArena *arena = _PyArena_New();
699+
if (!arena) {
700+
return -1;
701+
}
702+
703+
Py_ssize_t last_idx = 0;
704+
Py_ssize_t len = asdl_seq_LEN(e->v.TemplateStr.values);
705+
for (Py_ssize_t i = 0; i < len; i++) {
706+
expr_ty value = asdl_seq_GET(e->v.TemplateStr.values, i);
707+
708+
// Handle implicit concat of t-strings with f-strings
709+
if (value->kind == FormattedValue_kind) {
710+
if (i > last_idx) {
711+
// Create a new TemplateStr with the values between last_idx and i
712+
// and append it to the writer.
713+
if (_write_values_subarray(writer, e->v.TemplateStr.values,
714+
last_idx, i - 1, 't', arena) == -1) {
715+
goto error;
716+
}
717+
718+
if (append_charp(writer, " ") == -1) {
719+
goto error;
720+
}
721+
}
722+
723+
// Append the FormattedValue to the writer.
724+
if (_write_values_subarray(writer, e->v.TemplateStr.values,
725+
i, i, 'f', arena) == -1) {
726+
goto error;
727+
}
728+
729+
if (i + 1 < len) {
730+
if (append_charp(writer, " ") == -1) {
731+
goto error;
732+
}
733+
}
734+
735+
last_idx = i + 1;
736+
}
737+
}
738+
739+
if (last_idx < len) {
740+
if (_write_values_subarray(writer, e->v.TemplateStr.values,
741+
last_idx, len - 1, 't', arena) == -1) {
742+
goto error;
743+
}
744+
}
745+
746+
return 0;
747+
748+
error:
749+
_PyArena_Free(arena);
750+
return -1;
751+
}
752+
683753
static int
684754
append_joinedstr(PyUnicodeWriter *writer, expr_ty e, bool is_format_spec)
685755
{

0 commit comments

Comments
 (0)
0