]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-108843: fix ast.unparse for f-string with many quotes (#108981)
authorShantanu <12621235+hauntsaninja@users.noreply.github.com>
Mon, 18 Sep 2023 13:56:19 +0000 (06:56 -0700)
committerGitHub <noreply@github.com>
Mon, 18 Sep 2023 13:56:19 +0000 (14:56 +0100)
Lib/ast.py
Lib/test/test_unparse.py
Misc/NEWS.d/next/Library/2023-09-06-06-17-23.gh-issue-108843.WJMhsS.rst [new file with mode: 0644]

index 17ec7ff6f8bc12a5eed9800f64f2a51f221a6700..1f54309c8450d89a5b99323a17d60b1858c142a3 100644 (file)
@@ -1236,17 +1236,36 @@ class _Unparser(NodeVisitor):
 
         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}")
index 38c59e6d430b580e4cfb05f7c58c35680e0bb150..bdf7b0588bee67e3a0565c8c956cdf0f0ce70f0d 100644 (file)
@@ -635,6 +635,20 @@ class CosmeticTestCase(ASTTestCase):
         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 (file)
index 0000000..0f15761
--- /dev/null
@@ -0,0 +1 @@
+Fix an issue in :func:`ast.unparse` when unparsing f-strings containing many quote types.