Repository: psf/black

Issue:
Black --preview crashes on long dict key followed by a comment line
<!--
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. -->

**To Reproduce**

<!--
Minimal steps to reproduce the behavior with source code and Black's configuration.
-->

For example, take this code:

```python
a_dict = {
    "/this/is/a/very/very/very/very/very/very/very/very/very/very/long/key/without/spaces":
        # And there is a comment before the value
        ("item1", "item2", "item3"),
}
```

And run it with these arguments:

```sh
$ black --preview t.py
error: cannot format tests/data/preview/crash.py: list index out of range

Oh no! 💥 💔 💥
1 file would fail to reformat.
```

The resulting error is:

> cannot format file.py: INTERNAL ERROR: ...

**Expected behavior**

<!-- A clear and concise description of what you expected to happen. -->
Do not crash.

**Environment**

<!-- Please complete the following information: -->

- Black's version: this crash existed at least since v22.1.0

**Additional context**

<!-- Add any other context about the problem here. -->

IndexError is from this line: https://github.com/psf/black/blob/cd9fef8bab3a39dfba494f1917e4158faba439f9/src/black/trans.py#L1864

Simply guard the call fixes the crash but results in:

```python
a_dict = {
    "/this/is/a/very/very/very/very/very/very/very/very/very/very/long/key/without/spaces":
    # And there is a comment before the value
    ("item1", "item2", "item3"),
}
```

So there should be a better fix to also make the format nicer.


Discussion:
Doesn't reproduce on 23.1a1 (except in `--preview`). The stack trace is:

```
Traceback (most recent call last):
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/__init__.py", line 723, in reformat_code
    if format_stdin_to_stdout(
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/__init__.py", line 876, in format_stdin_to_stdout
    dst = format_file_contents(src, fast=fast, mode=mode)
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/__init__.py", line 926, in format_file_contents
    dst_contents = format_str(src_contents, mode=mode)
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/__init__.py", line 1073, in format_str
    dst_contents = _format_str_once(src_contents, mode=mode)
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/__init__.py", line 1103, in _format_str_once
    for line in transform_line(
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/linegen.py", line 561, in transform_line
    result = run_transformer(line, transform, mode, features, line_str=line_str)
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/linegen.py", line 1396, in run_transformer
    result.extend(transform_line(transformed_line, mode=mode, features=features))
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/linegen.py", line 561, in transform_line
    result = run_transformer(line, transform, mode, features, line_str=line_str)
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/linegen.py", line 1396, in run_transformer
    result.extend(transform_line(transformed_line, mode=mode, features=features))
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/linegen.py", line 561, in transform_line
    result = run_transformer(line, transform, mode, features, line_str=line_str)
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/linegen.py", line 1392, in run_transformer
    for transformed_line in transform(line, features):
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/trans.py", line 240, in __call__
    match_result = self.do_match(line)
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/trans.py", line 883, in do_match
    match_result = self.do_splitter_match(line)
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/trans.py", line 1688, in do_splitter_match
    or self._dict_or_lambda_match(LL)
  File "/tmp/rcvenv/lib/python3.10/site-packages/black/trans.py", line 1864, in _dict_or_lambda_match
    idx = i + 2 if is_empty_par(LL[i + 1]) else i + 1
IndexError: list index out of range
```
(Yep the crash is from ESP)

Resolving patch:
diff --git a/src/black/trans.py b/src/black/trans.py
--- a/src/black/trans.py
+++ b/src/black/trans.py
@@ -1866,7 +1866,7 @@ def _dict_or_lambda_match(LL: List[Leaf]) -> Optional[int]:
 
             for i, leaf in enumerate(LL):
                 # We MUST find a colon, it can either be dict's or lambda's colon...
-                if leaf.type == token.COLON:
+                if leaf.type == token.COLON and i < len(LL) - 1:
                     idx = i + 2 if is_empty_par(LL[i + 1]) else i + 1
 
                     # That colon MUST be followed by a string...


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
@@ -543,6 +543,13 @@ async def foo(self):
     f'xxxxxxxxxxxxx xxxx xx xxxxxxxxxx. xxxxx: {x.xxx}',
 )
 
+# Regression test for https://github.com/psf/black/issues/3455.
+a_dict = {
+    "/this/is/a/very/very/very/very/very/very/very/very/very/very/long/key/without/spaces":
+        # And there is a comment before the value
+        ("item1", "item2", "item3"),
+}
+
 
 # output
 
@@ -1221,3 +1228,10 @@ async def foo(self):
         f"xxxxxxxxxxxxx xxxx xx xxxxxxxxxx. xxxxx: {x.xxx}"
     ),
 )
+
+# Regression test for https://github.com/psf/black/issues/3455.
+a_dict = {
+    "/this/is/a/very/very/very/very/very/very/very/very/very/very/long/key/without/spaces":
+    # And there is a comment before the value
+    ("item1", "item2", "item3"),
+}