Repository: psf/black

Issue:
Black --preview crashes with nested long dict str keys
<!--
Please make sure that the bug is not already fixed either in newer versions or the
current development version. To confirm this, you have three options:

1. Update Black's version if a newer release exists: `pip install -U black`
2. Use the online formatter at <https://black.vercel.app/?version=main>, which will use
   the latest main branch.
3. Or run _Black_ on your machine:
   - create a new virtualenv (make sure it's the same Python version);
   - clone this repository;
   - run `pip install -e .[d]`;
   - run `pip install -r test_requirements.txt`
   - make sure it's sane by running `python -m pytest`; and
   - run `black` like you did last time.
-->

**Describe the bug**

<!-- A clear and concise description of what the bug is. -->

`black --preview` crashed on some code.

**To Reproduce**

<!--
Minimal steps to reproduce the behavior with source code and Black's configuration.
-->

For example, take this code:

```python
v = {
    ('a_very_long_str_a_very_long_str_a_very_long_str_a_very_long_str_'
     'a_very_long_str_a_very_long_str_a_very_long_str_'): {
        'key':
            'value',
    },
}
```

And run it with these arguments:

```sh
$ black file.py --preview
```

The resulting error is:

```
error: cannot format /Users/yileiyang/Downloads/t.py: Cannot parse: 3:122:         "a_very_long_str_a_very_long_str_a_very_long_str_a_very_long_str_a_very_long_str_a_very_long_str_a_very_long_str_": {

Oh no! 💥 💔 💥
1 file failed to reformat.
```

**Expected behavior**

<!-- A clear and concise description of what you expected to happen. -->

Should not crash.

**Environment**

<!-- Please complete the following information: -->

- Black's version: `22.8.1.dev4+g383b228`
- OS and Python version: macOS, Python 3.9.12

**Additional context**

<!-- Add any other context about the problem here. -->

Looks like this was caused by #3162, I'll take a look.


Discussion:

Resolving patch:
diff --git a/src/black/trans.py b/src/black/trans.py
--- a/src/black/trans.py
+++ b/src/black/trans.py
@@ -1071,6 +1071,16 @@ def _prefer_paren_wrap_match(LL: List[Leaf]) -> Optional[int]:
             # And the string is surrounded by commas (or is the first/last child)...
             prev_sibling = LL[0].prev_sibling
             next_sibling = LL[0].next_sibling
+            if (
+                not prev_sibling
+                and not next_sibling
+                and parent_type(LL[0]) == syms.atom
+            ):
+                # If it's an atom string, we need to check the parent atom's siblings.
+                parent = LL[0].parent
+                assert parent is not None  # For type checkers.
+                prev_sibling = parent.prev_sibling
+                next_sibling = parent.next_sibling
             if (not prev_sibling or prev_sibling.type == token.COMMA) and (
                 not next_sibling or next_sibling.type == token.COMMA
             ):


Test patch:
diff --git a/tests/data/preview/long_strings.py b/tests/data/preview/long_strings.py
--- a/tests/data/preview/long_strings.py
+++ b/tests/data/preview/long_strings.py
@@ -18,6 +18,14 @@
 
 D4 = {"A long and ridiculous {}".format(string_key): "This is a really really really long string that has to go i,side of a dictionary. It is soooo bad.", some_func("calling", "some", "stuff"): "This is a really really really long string that has to go inside of a dictionary. It is {soooo} bad (#{x}).".format(sooo="soooo", x=2), "A %s %s" % ("formatted", "string"): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)." % ("soooo", 2)}
 
+D5 = {  # Test for https://github.com/psf/black/issues/3261
+    ("This is a really long string that can't be expected to fit in one line and is used as a nested dict's key"): {"inner": "value"},
+}
+
+D6 = {  # Test for https://github.com/psf/black/issues/3261
+    ("This is a really long string that can't be expected to fit in one line and is used as a dict's key"): ["value1", "value2"],
+}
+
 L1 = ["The is a short string", "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a list literal, so it's expected to be wrapped in parens when spliting to avoid implicit str concatenation.", short_call("arg", {"key": "value"}), "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a list literal.", ("parens should be stripped for short string in list")]
 
 L2 = ["This is a really long string that can't be expected to fit in one line and is the only child of a list literal."]
@@ -357,6 +365,19 @@ def foo():
     % ("soooo", 2),
 }
 
+D5 = {  # Test for https://github.com/psf/black/issues/3261
+    "This is a really long string that can't be expected to fit in one line and is used as a nested dict's key": {
+        "inner": "value"
+    },
+}
+
+D6 = {  # Test for https://github.com/psf/black/issues/3261
+    "This is a really long string that can't be expected to fit in one line and is used as a dict's key": [
+        "value1",
+        "value2",
+    ],
+}
+
 L1 = [
     "The is a short string",
     (