Repository: psf/black

Issue:
Internal error in the formatting of the if expression within a type annotation

**Describe the bug**

The following code can not be parsed/formatted by black:

``` python
def name_3[name_0: [] if name_2 else name_3]():
    pass
```
([playground](https://black.vercel.app/?version=main&state=_Td6WFoAAATm1rRGAgAhARYAAAB0L-Wj4ADCAHJdAD2IimZxl1N_WlbvK5V-ruP2IyrEeQX_RICsvOepHM5Jzp63mgPveIl6GIPeU-OQPl2upYsIPTS1vS-WFO6s5SWmp3zENfvNkndF-o5hQPCDeu_RIHQYMBxA77utxqYykWeM-dJ_fzz9V-SC4y1I2rH1AAAAAHZXAfW2m40UAAGOAcMBAAAao7W_scRn-wIAAAAABFla))

black reported the following error:
```
> black -l 46 -t py313 bug.py
error: cannot format bug.py: INTERNAL ERROR: Black 25.1.1.dev21+g944a38e.d20250317 on Python (CPython) 3.13.1 produced code that is not equivalent to the source.  Please report a bug on https://github.com/psf/black/issues.  This diff might be helpful: /tmp/blk_0ajkvzbr.log

Oh no! 💥 💔 💥
1 file failed to reformat.
```

the reported diff in /tmp/blk_0ajkvzbr.log is:
``` diff
--- src
+++ dst
@@ -24,35 +24,41 @@
         type_comment=
         None,  # NoneType
         type_params=
         TypeVar(
             bound=
-            IfExp(
-                body=
-                List(
-                    ctx=
-                    Load(
-                    )  # /Load
-                    elts=
-                )  # /List
-                orelse=
-                Name(
-                    ctx=
-                    Load(
-                    )  # /Load
-                    id=
-                    'name_3',  # str
-                )  # /Name
-                test=
-                Name(
-                    ctx=
-                    Load(
-                    )  # /Load
-                    id=
-                    'name_2',  # str
-                )  # /Name
-            )  # /IfExp
+            Tuple(
+                ctx=
+                Load(
+                )  # /Load
+                elts=
+                IfExp(
+                    body=
+                    List(
+                        ctx=
+                        Load(
+                        )  # /Load
+                        elts=
+                    )  # /List
+                    orelse=
+                    Name(
+                        ctx=
+                        Load(
+                        )  # /Load
+                        id=
+                        'name_3',  # str
+                    )  # /Name
+                    test=
+                    Name(
+                        ctx=
+                        Load(
+                        )  # /Load
+                        id=
+                        'name_2',  # str
+                    )  # /Name
+                )  # /IfExp
+            )  # /Tuple
             default_value=
             None,  # NoneType
             name=
             'name_0',  # str
         )  # /TypeVar
```

but it can be parsed by cpython:
``` python
from ast import parse
parse(
    'def name_3[name_0: [] if name_2 else name_3]():\n'
    '    pass\n'
)
```

The code can be formatted with `black -l 46 -t py313 bug.py --fast`:
``` python
def name_3[name_0: (
    [] if name_2 else name_3,
)]():
    pass
```

**Environment**

<!-- Please complete the following information: -->

- Black's version: current main (d0ff3bd6cb82e35b1529155d29fca2c13442e68d)
- OS and Python version: Linux/Python 3.13.1 (main, Jan 14 2025, 22:47:38) [Clang 19.1.6 ]

**Additional context**

The bug was found by pysource-codegen (see #3908)
The above problem description was created from a script,
let me know if you think it can be improved.


Discussion:
yes it is, can  you explain better what you driving at pls?
I'm the author of pysource-codegen and used it to do some fuzz testing with black (see #3908).

I wrote a small script which searches for bugs like the one above and generates this report. The goal is to integrate these tests in the CI of black and to be able to find issues before they are found by some user.

Cases like the above are real edge cases which are unlikely to be a problem in the real world, but you can not wish to find the important one without fixing the edge cases too.

I am interested in working on this. Can I make a PR ??



Patch:
diff --git a/CHANGES.md b/CHANGES.md
index 4bf98d87cfe..4e97c1f2e46 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -21,6 +21,7 @@
 - Fix crash on `await ...` (where `...` is a literal `Ellipsis`) (#4676)
 - Remove support for pre-python 3.7 `await/async` as soft keywords/variable names
   (#4676)
+- Fix crash on parenthesized expression inside a type parameter bound (#4684)
 
 ### Preview style
 
diff --git a/src/black/linegen.py b/src/black/linegen.py
index fa574ca215e..032ab687835 100644
--- a/src/black/linegen.py
+++ b/src/black/linegen.py
@@ -787,12 +787,26 @@ def left_hand_split(
         head_leaves: list[Leaf] = []
         current_leaves = head_leaves
         matching_bracket: Optional[Leaf] = None
-        for leaf in line.leaves:
+        depth = 0
+        for index, leaf in enumerate(line.leaves):
+            if index == 2 and leaf.type == token.LSQB:
+                # A [ at index 2 means this is a type param, so start
+                # tracking the depth
+                depth += 1
+            elif depth > 0:
+                if leaf.type == token.LSQB:
+                    depth += 1
+                elif leaf.type == token.RSQB:
+                    depth -= 1
             if (
                 current_leaves is body_leaves
                 and leaf.type in CLOSING_BRACKETS
                 and leaf.opening_bracket is matching_bracket
                 and isinstance(matching_bracket, Leaf)
+                # If the code is still on LPAR and we are inside a type
+                # param, ignore the match since this is searching
+                # for the function arguments
+                and not (leaf_type == token.LPAR and depth > 0)
             ):
                 ensure_visible(leaf)
                 ensure_visible(matching_bracket)


Test patch:
diff --git a/tests/data/cases/type_params.py b/tests/data/cases/type_params.py
index f8fc3855741..124292d6d54 100644
--- a/tests/data/cases/type_params.py
+++ b/tests/data/cases/type_params.py
@@ -15,6 +15,8 @@ def magic[Trailing, Comma,](): pass
 
 def weird_syntax[T: lambda: 42, U: a or b](): pass
 
+def name_3[name_0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa if aaaaaaaaaaa else name_3](): pass
+
 # output
 
 
@@ -62,3 +64,9 @@ def magic[
 
 def weird_syntax[T: lambda: 42, U: a or b]():
     pass
+
+
+def name_3[
+    name_0: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa if aaaaaaaaaaa else name_3
+]():
+    pass