Repository: psf/black

Issue:
--preview flag produces INTERNAL ERROR: f-string expression part cannot include a backslash
**Black v23.1a1**

[Playground link](https://black.vercel.app/?version=stable&state=_Td6WFoAAATm1rRGAgAhARYAAAB0L-Wj4ADsAK5dAD2IimZxl1N_WmBpTZyX5zjgLwP-pyIm8_YgoORJOyuCI4dyGZ4Eo2yAlj2yWKy6dU9uLlNMDZT5M_vwLAHomJrsqP0VuqtlmOtCa4uAlWWj7Ljr4x2m5az1KP-ySgL-Wg5653IMmVj5ye6HAeAJj_yGAeE2XZzjHzjn6HsMY4oyoRT6xRv3OeBMxJBHvKMDce1Wq6lwf4buFV5kDRAKhQm6IBBDKsI6s3IZVKQtFwAAAGCHAVtMWPnGAAHKAe0BAABtM5FrscRn-wIAAAAABFla)

## Options

`--line-length=80`
`--safe`
`--preview`

## Input

```python
my_dict = {"foo": "bar"}
s= f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:\'{my_dict["foo"]}\''

```

## Output

```python
INTERNAL ERROR: Black produced invalid code: f-string expression part cannot include a backslash (<unknown>, line 5). Please report a bug on https://github.com/psf/black/issues.  This invalid output might be helpful: /tmp/blk_5qbr6zhy.log

  File "src/black/__init__.py", line 1324, in assert_equivalent
  File "src/black/parsing.py", line 183, in parse_ast
my_dict = {"foo": "bar"}
s = (
    "Lorem Ipsum is simply dummy text of the printing and typesetting"
    f" industry:'{my_dict[\"foo\"]}'"
)

```

## Expected
```python
my_dict = {"foo": "bar"}
s = (
    "Lorem Ipsum is simply dummy text of the printing and typesetting"
    f" industry:'{my_dict['foo']}'"
)

```


Discussion:
I'll take a look at this.

Resolving patch:
diff --git a/src/black/trans.py b/src/black/trans.py
--- a/src/black/trans.py
+++ b/src/black/trans.py
@@ -572,6 +572,12 @@ def make_naked(string: str, string_prefix: str) -> str:
                 characters have been escaped.
             """
             assert_is_leaf_string(string)
+            if "f" in string_prefix:
+                string = _toggle_fexpr_quotes(string, QUOTE)
+                # After quotes toggling, quotes in expressions won't be escaped
+                # because quotes can't be reused in f-strings. So we can simply
+                # let the escaping logic below run without knowing f-string
+                # expressions.
 
             RE_EVEN_BACKSLASHES = r"(?:(?<!\\)(?:\\\\)*)"
             naked_string = string[len(string_prefix) + 1 : -1]
@@ -1240,6 +1246,30 @@ def fstring_contains_expr(s: str) -> bool:
     return any(iter_fexpr_spans(s))
 
 
+def _toggle_fexpr_quotes(fstring: str, old_quote: str) -> str:
+    """
+    Toggles quotes used in f-string expressions that are `old_quote`.
+
+    f-string expressions can't contain backslashes, so we need to toggle the
+    quotes if the f-string itself will end up using the same quote. We can
+    simply toggle without escaping because, quotes can't be reused in f-string
+    expressions. They will fail to parse.
+
+    NOTE: If PEP 701 is accepted, above statement will no longer be true.
+    Though if quotes can be reused, we can simply reuse them without updates or
+    escaping, once Black figures out how to parse the new grammar.
+    """
+    new_quote = "'" if old_quote == '"' else '"'
+    parts = []
+    previous_index = 0
+    for start, end in iter_fexpr_spans(fstring):
+        parts.append(fstring[previous_index:start])
+        parts.append(fstring[start:end].replace(old_quote, new_quote))
+        previous_index = end
+    parts.append(fstring[previous_index:])
+    return "".join(parts)
+
+
 class StringSplitter(BaseStringSplitter, CustomSplitMapMixin):
     """
     StringTransformer that splits "atom" strings (i.e. strings which exist on


Test patch:
diff --git a/tests/data/preview/long_strings__regression.py b/tests/data/preview/long_strings__regression.py
--- a/tests/data/preview/long_strings__regression.py
+++ b/tests/data/preview/long_strings__regression.py
@@ -550,6 +550,16 @@ async def foo(self):
         ("item1", "item2", "item3"),
 }
 
+# Regression test for https://github.com/psf/black/issues/3506.
+s = (
+    "With single quote: ' "
+    f" {my_dict['foo']}"
+    ' With double quote: " '
+    f' {my_dict["bar"]}'
+)
+
+s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:\'{my_dict["foo"]}\''
+
 
 # output
 
@@ -1235,3 +1245,11 @@ async def foo(self):
     # And there is a comment before the value
     ("item1", "item2", "item3"),
 }
+
+# Regression test for https://github.com/psf/black/issues/3506.
+s = f"With single quote: '  {my_dict['foo']} With double quote: \"  {my_dict['bar']}"
+
+s = (
+    "Lorem Ipsum is simply dummy text of the printing and typesetting"
+    f" industry:'{my_dict['foo']}'"
+)