From 0fe458ecc46d2f2a2e82e324d860dff932d848d4 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Mon, 18 Sep 2023 06:56:19 -0700 Subject: [PATCH] gh-108843: fix ast.unparse for f-string with many quotes (#108981) --- Lib/ast.py | 21 ++++++++++++++++++- Lib/test/test_unparse.py | 14 +++++++++++++ ...-09-06-06-17-23.gh-issue-108843.WJMhsS.rst | 1 + 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-09-06-06-17-23.gh-issue-108843.WJMhsS.rst diff --git a/Lib/ast.py b/Lib/ast.py index 17ec7ff6f8bc12..1f54309c8450d8 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -1236,17 +1236,36 @@ def visit_JoinedStr(self, node): new_fstring_parts = [] quote_types = list(_ALL_QUOTES) + fallback_to_repr = False for value, is_constant in fstring_parts: if is_constant: - value, quote_types = self._str_literal_helper( + value, new_quote_types = self._str_literal_helper( value, quote_types=quote_types, escape_special_whitespace=True, ) + if set(new_quote_types).isdisjoint(quote_types): + fallback_to_repr = True + break + quote_types = new_quote_types elif "\n" in value: quote_types = [q for q in quote_types if q in _MULTI_QUOTES] + assert quote_types new_fstring_parts.append(value) + if fallback_to_repr: + # If we weren't able to find a quote type that works for all parts + # of the JoinedStr, fallback to using repr and triple single quotes. + quote_types = ["'''"] + new_fstring_parts.clear() + for value, is_constant in fstring_parts: + if is_constant: + value = repr('"' + value) # force repr to use single quotes + expected_prefix = "'\"" + assert value.startswith(expected_prefix), repr(value) + value = value[len(expected_prefix):-1] + new_fstring_parts.append(value) + value = "".join(new_fstring_parts) quote_type = quote_types[0] self.write(f"{quote_type}{value}{quote_type}") diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index 38c59e6d430b58..bdf7b0588bee67 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -635,6 +635,20 @@ def test_star_expr_assign_target_multiple(self): self.check_src_roundtrip("[a, b] = [c, d] = [e, f] = g") self.check_src_roundtrip("a, b = [c, d] = e, f = g") + def test_multiquote_joined_string(self): + self.check_ast_roundtrip("f\"'''{1}\\\"\\\"\\\"\" ") + self.check_ast_roundtrip("""f"'''{1}""\\"" """) + self.check_ast_roundtrip("""f'""\"{1}''' """) + self.check_ast_roundtrip("""f'""\"{1}""\\"' """) + + self.check_ast_roundtrip("""f"'''{"\\n"}""\\"" """) + self.check_ast_roundtrip("""f'""\"{"\\n"}''' """) + self.check_ast_roundtrip("""f'""\"{"\\n"}""\\"' """) + + self.check_ast_roundtrip("""f'''""\"''\\'{"\\n"}''' """) + self.check_ast_roundtrip("""f'''""\"''\\'{"\\n\\"'"}''' """) + self.check_ast_roundtrip("""f'''""\"''\\'{""\"\\n\\"'''""\" '''\\n'''}''' """) + class ManualASTCreationTestCase(unittest.TestCase): """Test that AST nodes created without a type_params field unparse correctly.""" diff --git a/Misc/NEWS.d/next/Library/2023-09-06-06-17-23.gh-issue-108843.WJMhsS.rst b/Misc/NEWS.d/next/Library/2023-09-06-06-17-23.gh-issue-108843.WJMhsS.rst new file mode 100644 index 00000000000000..0f15761c14bb7d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-09-06-06-17-23.gh-issue-108843.WJMhsS.rst @@ -0,0 +1 @@ +Fix an issue in :func:`ast.unparse` when unparsing f-strings containing many quote types.