Repository: psf/black

Issue:
Remove braces around except (PEP 758)
**Describe the style change**
With PEP 758 and python 3.14 its possible to remove the braces around a except block.
I would like to propose that black will do this.

**Examples in the current _Black_ style**
```python
try:
    pass
except (ValueError, TypeError):
    pass
```

**Desired style**
```python
try:
    pass
except ValueError, TypeError:
    pass
```

**Additional context**
https://peps.python.org/pep-0758/



Discussion:
I'd like to take this up. Let me know if that's okay.
Go ahead @ranjodhsingh1729 
Hi, I'm just wrapping up my work on this issue and had a quick question. Since this is the first time we're incorporating a Python 3.14-specific feature, I wanted to check how we typically handle restricting such changes to only apply when targeting Python 3.14 or higher. What's the recommended approach for this?
Take a look at the `--target-version` CLI flag, see if that helps.
Thanks Will do



Patch:
diff --git a/CHANGES.md b/CHANGES.md
index da991a69eee..3179a5ed76e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -32,6 +32,8 @@
 - Improve `multiline_string_handling` with ternaries and dictionaries (#4657)
 - Fix a bug where `string_processing` would not split f-strings directly after
   expressions (#4680)
+- Remove parentheses around multiple exception types in `except` and `except*` without
+  `as`. (#4720)
 
 ### Configuration
 
diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md
index b5eb3690f87..45ec1e67ed8 100644
--- a/docs/the_black_code_style/future_style.md
+++ b/docs/the_black_code_style/future_style.md
@@ -29,6 +29,8 @@ Currently, the following features are included in the preview style:
 - `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behaviour on one-liner declarations,
   such as `def foo(): return "mock"  # fmt: skip`, where previously the declaration
   would have been incorrectly collapsed.
+- `remove_parens_around_except_types`: Remove parentheses around multiple exception
+  types in `except` and `except*` without `as`. See PEP 758 for details.
 
 (labels/unstable-features)=
 
diff --git a/src/black/__init__.py b/src/black/__init__.py
index 48d2bb8154b..082f705f196 100644
--- a/src/black/__init__.py
+++ b/src/black/__init__.py
@@ -1225,9 +1225,12 @@ def _format_str_once(
         future_imports = get_future_imports(src_node)
         versions = detect_target_versions(src_node, future_imports=future_imports)
 
-    context_manager_features = {
+    line_generation_features = {
         feature
-        for feature in {Feature.PARENTHESIZED_CONTEXT_MANAGERS}
+        for feature in {
+            Feature.PARENTHESIZED_CONTEXT_MANAGERS,
+            Feature.UNPARENTHESIZED_EXCEPT_TYPES,
+        }
         if supports_feature(versions, feature)
     }
     normalize_fmt_off(src_node, mode, lines)
@@ -1235,7 +1238,7 @@ def _format_str_once(
         # This should be called after normalize_fmt_off.
         convert_unchanged_lines(src_node, lines)
 
-    line_generator = LineGenerator(mode=mode, features=context_manager_features)
+    line_generator = LineGenerator(mode=mode, features=line_generation_features)
     elt = EmptyLineTracker(mode=mode)
     split_line_features = {
         feature
@@ -1395,13 +1398,6 @@ def get_features_used(  # noqa: C901
         elif n.type == syms.match_stmt:
             features.add(Feature.PATTERN_MATCHING)
 
-        elif (
-            n.type == syms.except_clause
-            and len(n.children) >= 2
-            and n.children[1].type == token.STAR
-        ):
-            features.add(Feature.EXCEPT_STAR)
-
         elif n.type in {syms.subscriptlist, syms.trailer} and any(
             child.type == syms.star_expr for child in n.children
         ):
@@ -1423,6 +1419,32 @@ def get_features_used(  # noqa: C901
         ):
             features.add(Feature.TYPE_PARAM_DEFAULTS)
 
+        elif (
+            n.type == syms.except_clause
+            and len(n.children) >= 2
+            and (
+                n.children[1].type == token.STAR or n.children[1].type == syms.testlist
+            )
+        ):
+            is_star_except = n.children[1].type == token.STAR
+
+            if is_star_except:
+                features.add(Feature.EXCEPT_STAR)
+
+            # Presence of except* pushes as clause 1 index back
+            has_as_clause = (
+                len(n.children) >= is_star_except + 3
+                and n.children[is_star_except + 2].type == token.NAME
+                and n.children[is_star_except + 2].value == "as"  # type: ignore
+            )
+
+            # If there's no 'as' clause and the except expression is a testlist.
+            if not has_as_clause and (
+                (is_star_except and n.children[2].type == syms.testlist)
+                or (not is_star_except and n.children[1].type == syms.testlist)
+            ):
+                features.add(Feature.UNPARENTHESIZED_EXCEPT_TYPES)
+
     return features
 
 
diff --git a/src/black/linegen.py b/src/black/linegen.py
index 032ab687835..49fab818a6d 100644
--- a/src/black/linegen.py
+++ b/src/black/linegen.py
@@ -250,6 +250,8 @@ def visit_dictsetmaker(self, node: Node) -> Iterator[Line]:
                         maybe_make_parens_invisible_in_atom(
                             child,
                             parent=node,
+                            mode=self.mode,
+                            features=self.features,
                             remove_brackets_around_comma=False,
                         )
                     else:
@@ -270,6 +272,8 @@ def visit_funcdef(self, node: Node) -> Iterator[Line]:
                     if maybe_make_parens_invisible_in_atom(
                         child,
                         parent=node,
+                        mode=self.mode,
+                        features=self.features,
                         remove_brackets_around_comma=False,
                     ):
                         wrap_in_parentheses(node, child, visible=False)
@@ -363,7 +367,7 @@ def visit_power(self, node: Node) -> Iterator[Line]:
             ):
                 wrap_in_parentheses(node, leaf)
 
-        remove_await_parens(node)
+        remove_await_parens(node, mode=self.mode, features=self.features)
 
         yield from self.visit_default(node)
 
@@ -410,7 +414,9 @@ def foo(a: int, b: float = 7): ...
         def foo(a: (int), b: (float) = 7): ...
         """
         assert len(node.children) == 3
-        if maybe_make_parens_invisible_in_atom(node.children[2], parent=node):
+        if maybe_make_parens_invisible_in_atom(
+            node.children[2], parent=node, mode=self.mode, features=self.features
+        ):
             wrap_in_parentheses(node, node.children[2], visible=False)
 
         yield from self.visit_default(node)
@@ -516,7 +522,12 @@ def visit_atom(self, node: Node) -> Iterator[Line]:
                 first.type == token.LBRACE and last.type == token.RBRACE
             ):
                 # Lists or sets of one item
-                maybe_make_parens_invisible_in_atom(node.children[1], parent=node)
+                maybe_make_parens_invisible_in_atom(
+                    node.children[1],
+                    parent=node,
+                    mode=self.mode,
+                    features=self.features,
+                )
 
         yield from self.visit_default(node)
 
@@ -1448,15 +1459,16 @@ def normalize_invisible_parens(  # noqa: C901
                 if maybe_make_parens_invisible_in_atom(
                     child,
                     parent=node,
+                    mode=mode,
+                    features=features,
                     remove_brackets_around_comma=True,
                 ):
                     wrap_in_parentheses(node, child, visible=False)
             elif isinstance(child, Node) and node.type == syms.with_stmt:
-                remove_with_parens(child, node)
+                remove_with_parens(child, node, mode=mode, features=features)
             elif child.type == syms.atom:
                 if maybe_make_parens_invisible_in_atom(
-                    child,
-                    parent=node,
+                    child, parent=node, mode=mode, features=features
                 ):
                     wrap_in_parentheses(node, child, visible=False)
             elif is_one_tuple(child):
@@ -1508,7 +1520,7 @@ def _normalize_import_from(parent: Node, child: LN, index: int) -> None:
         parent.append_child(Leaf(token.RPAR, ""))
 
 
-def remove_await_parens(node: Node) -> None:
+def remove_await_parens(node: Node, mode: Mode, features: Collection[Feature]) -> None:
     if node.children[0].type == token.AWAIT and len(node.children) > 1:
         if (
             node.children[1].type == syms.atom
@@ -1517,6 +1529,8 @@ def remove_await_parens(node: Node) -> None:
             if maybe_make_parens_invisible_in_atom(
                 node.children[1],
                 parent=node,
+                mode=mode,
+                features=features,
                 remove_brackets_around_comma=True,
             ):
                 wrap_in_parentheses(node, node.children[1], visible=False)
@@ -1585,7 +1599,9 @@ def _maybe_wrap_cms_in_parens(
         node.insert_child(1, new_child)
 
 
-def remove_with_parens(node: Node, parent: Node) -> None:
+def remove_with_parens(
+    node: Node, parent: Node, mode: Mode, features: Collection[Feature]
+) -> None:
     """Recursively hide optional parens in `with` statements."""
     # Removing all unnecessary parentheses in with statements in one pass is a tad
     # complex as different variations of bracketed statements result in pretty
@@ -1607,21 +1623,25 @@ def remove_with_parens(node: Node, parent: Node) -> None:
         if maybe_make_parens_invisible_in_atom(
             node,
             parent=parent,
+            mode=mode,
+            features=features,
             remove_brackets_around_comma=True,
         ):
             wrap_in_parentheses(parent, node, visible=False)
         if isinstance(node.children[1], Node):
-            remove_with_parens(node.children[1], node)
+            remove_with_parens(node.children[1], node, mode=mode, features=features)
     elif node.type == syms.testlist_gexp:
         for child in node.children:
             if isinstance(child, Node):
-                remove_with_parens(child, node)
+                remove_with_parens(child, node, mode=mode, features=features)
     elif node.type == syms.asexpr_test and not any(
         leaf.type == token.COLONEQUAL for leaf in node.leaves()
     ):
         if maybe_make_parens_invisible_in_atom(
             node.children[0],
             parent=node,
+            mode=mode,
+            features=features,
             remove_brackets_around_comma=True,
         ):
             wrap_in_parentheses(node, node.children[0], visible=False)
@@ -1630,6 +1650,8 @@ def remove_with_parens(node: Node, parent: Node) -> None:
 def maybe_make_parens_invisible_in_atom(
     node: LN,
     parent: LN,
+    mode: Mode,
+    features: Collection[Feature],
     remove_brackets_around_comma: bool = False,
 ) -> bool:
     """If it's safe, make the parens in the atom `node` invisible, recursively.
@@ -1655,6 +1677,25 @@ def maybe_make_parens_invisible_in_atom(
             # and option to skip this check for `for` and `with` statements.
             not remove_brackets_around_comma
             and max_delimiter_priority_in_atom(node) >= COMMA_PRIORITY
+            # Skip this check in Preview mode in order to
+            # Remove parentheses around multiple exception types in except and
+            # except* without as. See PEP 758 for details.
+            and not (
+                Preview.remove_parens_around_except_types in mode
+                and Feature.UNPARENTHESIZED_EXCEPT_TYPES in features
+                # is a tuple
+                and is_tuple(node)
+                # has a parent node
+                and node.parent is not None
+                # parent is an except clause
+                and node.parent.type == syms.except_clause
+                # is not immediately followed by as clause
+                and not (
+                    node.next_sibling is not None
+                    and is_name_token(node.next_sibling)
+                    and node.next_sibling.value == "as"
+                )
+            )
         )
         or is_tuple_containing_walrus(node)
         or is_tuple_containing_star(node)
@@ -1695,6 +1736,8 @@ def maybe_make_parens_invisible_in_atom(
         maybe_make_parens_invisible_in_atom(
             middle,
             parent=parent,
+            mode=mode,
+            features=features,
             remove_brackets_around_comma=remove_brackets_around_comma,
         )
 
diff --git a/src/black/mode.py b/src/black/mode.py
index 362607efc86..86e0bfcb1c2 100644
--- a/src/black/mode.py
+++ b/src/black/mode.py
@@ -25,6 +25,7 @@ class TargetVersion(Enum):
     PY311 = 11
     PY312 = 12
     PY313 = 13
+    PY314 = 14
 
     def pretty(self) -> str:
         assert self.name[:2] == "PY"
@@ -53,6 +54,7 @@ class Feature(Enum):
     TYPE_PARAMS = 18
     FSTRING_PARSING = 19
     TYPE_PARAM_DEFAULTS = 20
+    UNPARENTHESIZED_EXCEPT_TYPES = 21
     FORCE_OPTIONAL_PARENTHESES = 50
 
     # __future__ flags
@@ -186,10 +188,35 @@ class Feature(Enum):
         Feature.FSTRING_PARSING,
         Feature.TYPE_PARAM_DEFAULTS,
     },
+    TargetVersion.PY314: {
+        Feature.F_STRINGS,
+        Feature.DEBUG_F_STRINGS,
+        Feature.NUMERIC_UNDERSCORES,
+        Feature.TRAILING_COMMA_IN_CALL,
+        Feature.TRAILING_COMMA_IN_DEF,
+        Feature.ASYNC_KEYWORDS,
+        Feature.FUTURE_ANNOTATIONS,
+        Feature.ASSIGNMENT_EXPRESSIONS,
+        Feature.RELAXED_DECORATORS,
+        Feature.POS_ONLY_ARGUMENTS,
+        Feature.UNPACKING_ON_FLOW,
+        Feature.ANN_ASSIGN_EXTENDED_RHS,
+        Feature.PARENTHESIZED_CONTEXT_MANAGERS,
+        Feature.PATTERN_MATCHING,
+        Feature.EXCEPT_STAR,
+        Feature.VARIADIC_GENERICS,
+        Feature.TYPE_PARAMS,
+        Feature.FSTRING_PARSING,
+        Feature.TYPE_PARAM_DEFAULTS,
+        Feature.UNPARENTHESIZED_EXCEPT_TYPES,
+    },
 }
 
 
 def supports_feature(target_versions: set[TargetVersion], feature: Feature) -> bool:
+    if not target_versions:
+        raise ValueError("target_versions must not be empty")
+
     return all(feature in VERSION_TO_FEATURES[version] for version in target_versions)
 
 
@@ -204,6 +231,9 @@ class Preview(Enum):
     multiline_string_handling = auto()
     always_one_newline_after_import = auto()
     fix_fmt_skip_in_one_liners = auto()
+    # Remove parentheses around multiple exception types in except and
+    # except* without as. See PEP 758 for details.
+    remove_parens_around_except_types = auto()
 
 
 UNSTABLE_FEATURES: set[Preview] = {
diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json
index 572e5bbfa1e..b342f2d2204 100644
--- a/src/black/resources/black.schema.json
+++ b/src/black/resources/black.schema.json
@@ -28,7 +28,8 @@
           "py310",
           "py311",
           "py312",
-          "py313"
+          "py313",
+          "py314"
         ]
       },
       "description": "Python versions that should be supported by Black's output. You should include all versions that your code supports. By default, Black will infer target versions from the project metadata in pyproject.toml. If this does not yield conclusive results, Black will use per-file auto-detection."
@@ -84,7 +85,8 @@
           "wrap_long_dict_values_in_parens",
           "multiline_string_handling",
           "always_one_newline_after_import",
-          "fix_fmt_skip_in_one_liners"
+          "fix_fmt_skip_in_one_liners",
+          "remove_parens_around_except_types"
         ]
       },
       "description": "Enable specific features included in the `--unstable` style. Requires `--preview`. No compatibility guarantees are provided on the behavior or existence of any unstable features."
diff --git a/src/blib2to3/Grammar.txt b/src/blib2to3/Grammar.txt
index b779b49eefb..406a21f764d 100644
--- a/src/blib2to3/Grammar.txt
+++ b/src/blib2to3/Grammar.txt
@@ -123,7 +123,7 @@ try_stmt: ('try' ':' suite
 with_stmt: 'with' asexpr_test (',' asexpr_test)*  ':' suite
 
 # NB compile.c makes sure that the default except clause is last
-except_clause: 'except' ['*'] [test [(',' | 'as') test]]
+except_clause: 'except' ['*'] [testlist ['as' test]]
 suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
 
 # Backward compatibility cruft to support:


Test patch:
diff --git a/tests/data/cases/remove_except_types_parens.py b/tests/data/cases/remove_except_types_parens.py
new file mode 100644
index 00000000000..71f2d229d3a
--- /dev/null
+++ b/tests/data/cases/remove_except_types_parens.py
@@ -0,0 +1,249 @@
+# flags: --preview --minimum-version=3.14
+# SEE PEP 758 FOR MORE DETAILS
+# remains unchanged
+try:
+    pass
+except:
+    pass
+
+# remains unchanged
+try:
+    pass
+except ValueError:
+    pass
+
+try:
+    pass
+except* ValueError:
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except (ValueError):
+    pass
+
+try:
+    pass
+except* (ValueError):
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except (ValueError) as e:
+    pass
+
+try:
+    pass
+except* (ValueError) as e:
+    pass
+
+# remains unchanged
+try:
+    pass
+except (ValueError,):
+    pass
+
+try:
+    pass
+except* (ValueError,):
+    pass
+
+# remains unchanged
+try:
+    pass
+except (ValueError,) as e:
+    pass
+
+try:
+    pass
+except* (ValueError,) as e:
+    pass
+
+# remains unchanged
+try:
+    pass
+except ValueError, TypeError, KeyboardInterrupt:
+    pass
+
+try:
+    pass
+except* ValueError, TypeError, KeyboardInterrupt:
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except (ValueError, TypeError, KeyboardInterrupt):
+    pass
+
+try:
+    pass
+except* (ValueError, TypeError, KeyboardInterrupt):
+    pass
+
+# parenthesis are not removed
+try:
+    pass
+except (ValueError, TypeError, KeyboardInterrupt) as e:
+    pass
+
+try:
+    pass
+except* (ValueError, TypeError, KeyboardInterrupt) as e:
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except (ValueError if True else TypeError):
+    pass
+
+try:
+    pass
+except* (ValueError if True else TypeError):
+    pass
+
+# inner except: parenthesis are removed
+# outer except: parenthsis are not removed
+try:
+    try:
+        pass
+    except (TypeError, KeyboardInterrupt):
+        pass
+except (ValueError,):
+    pass
+
+try:
+    try:
+        pass
+    except* (TypeError, KeyboardInterrupt):
+        pass
+except* (ValueError,):
+    pass
+
+# output
+# SEE PEP 758 FOR MORE DETAILS
+# remains unchanged
+try:
+    pass
+except:
+    pass
+
+# remains unchanged
+try:
+    pass
+except ValueError:
+    pass
+
+try:
+    pass
+except* ValueError:
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except ValueError:
+    pass
+
+try:
+    pass
+except* ValueError:
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except ValueError as e:
+    pass
+
+try:
+    pass
+except* ValueError as e:
+    pass
+
+# remains unchanged
+try:
+    pass
+except (ValueError,):
+    pass
+
+try:
+    pass
+except* (ValueError,):
+    pass
+
+# remains unchanged
+try:
+    pass
+except (ValueError,) as e:
+    pass
+
+try:
+    pass
+except* (ValueError,) as e:
+    pass
+
+# remains unchanged
+try:
+    pass
+except ValueError, TypeError, KeyboardInterrupt:
+    pass
+
+try:
+    pass
+except* ValueError, TypeError, KeyboardInterrupt:
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except ValueError, TypeError, KeyboardInterrupt:
+    pass
+
+try:
+    pass
+except* ValueError, TypeError, KeyboardInterrupt:
+    pass
+
+# parenthesis are not removed
+try:
+    pass
+except (ValueError, TypeError, KeyboardInterrupt) as e:
+    pass
+
+try:
+    pass
+except* (ValueError, TypeError, KeyboardInterrupt) as e:
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except ValueError if True else TypeError:
+    pass
+
+try:
+    pass
+except* ValueError if True else TypeError:
+    pass
+
+# inner except: parenthesis are removed
+# outer except: parenthsis are not removed
+try:
+    try:
+        pass
+    except TypeError, KeyboardInterrupt:
+        pass
+except (ValueError,):
+    pass
+
+try:
+    try:
+        pass
+    except* TypeError, KeyboardInterrupt:
+        pass
+except* (ValueError,):
+    pass
diff --git a/tests/data/cases/remove_except_types_parens_pre_py314.py b/tests/data/cases/remove_except_types_parens_pre_py314.py
new file mode 100644
index 00000000000..9f3a3b25652
--- /dev/null
+++ b/tests/data/cases/remove_except_types_parens_pre_py314.py
@@ -0,0 +1,225 @@
+# flags: --preview --minimum-version=3.11
+# SEE PEP 758 FOR MORE DETAILS
+# remains unchanged
+try:
+    pass
+except:
+    pass
+
+# remains unchanged
+try:
+    pass
+except ValueError:
+    pass
+
+try:
+    pass
+except* ValueError:
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except (ValueError):
+    pass
+
+try:
+    pass
+except* (ValueError):
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except (ValueError) as e:
+    pass
+
+try:
+    pass
+except* (ValueError) as e:
+    pass
+
+# remains unchanged
+try:
+    pass
+except (ValueError,):
+    pass
+
+try:
+    pass
+except* (ValueError,):
+    pass
+
+# remains unchanged
+try:
+    pass
+except (ValueError,) as e:
+    pass
+
+try:
+    pass
+except* (ValueError,) as e:
+    pass
+
+# parenthesis are not removed
+try:
+    pass
+except (ValueError, TypeError, KeyboardInterrupt):
+    pass
+
+try:
+    pass
+except* (ValueError, TypeError, KeyboardInterrupt):
+    pass
+
+# parenthesis are not removed
+try:
+    pass
+except (ValueError, TypeError, KeyboardInterrupt) as e:
+    pass
+
+try:
+    pass
+except* (ValueError, TypeError, KeyboardInterrupt) as e:
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except (ValueError if True else TypeError):
+    pass
+
+try:
+    pass
+except* (ValueError if True else TypeError):
+    pass
+
+# parenthesis are not removed
+try:
+    try:
+        pass
+    except (TypeError, KeyboardInterrupt):
+        pass
+except (ValueError,):
+    pass
+
+try:
+    try:
+        pass
+    except* (TypeError, KeyboardInterrupt):
+        pass
+except* (ValueError,):
+    pass
+
+# output
+# SEE PEP 758 FOR MORE DETAILS
+# remains unchanged
+try:
+    pass
+except:
+    pass
+
+# remains unchanged
+try:
+    pass
+except ValueError:
+    pass
+
+try:
+    pass
+except* ValueError:
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except ValueError:
+    pass
+
+try:
+    pass
+except* ValueError:
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except ValueError as e:
+    pass
+
+try:
+    pass
+except* ValueError as e:
+    pass
+
+# remains unchanged
+try:
+    pass
+except (ValueError,):
+    pass
+
+try:
+    pass
+except* (ValueError,):
+    pass
+
+# remains unchanged
+try:
+    pass
+except (ValueError,) as e:
+    pass
+
+try:
+    pass
+except* (ValueError,) as e:
+    pass
+
+# parenthesis are not removed
+try:
+    pass
+except (ValueError, TypeError, KeyboardInterrupt):
+    pass
+
+try:
+    pass
+except* (ValueError, TypeError, KeyboardInterrupt):
+    pass
+
+# parenthesis are not removed
+try:
+    pass
+except (ValueError, TypeError, KeyboardInterrupt) as e:
+    pass
+
+try:
+    pass
+except* (ValueError, TypeError, KeyboardInterrupt) as e:
+    pass
+
+# parenthesis are removed
+try:
+    pass
+except ValueError if True else TypeError:
+    pass
+
+try:
+    pass
+except* ValueError if True else TypeError:
+    pass
+
+# parenthesis are not removed
+try:
+    try:
+        pass
+    except (TypeError, KeyboardInterrupt):
+        pass
+except (ValueError,):
+    pass
+
+try:
+    try:
+        pass
+    except* (TypeError, KeyboardInterrupt):
+        pass
+except* (ValueError,):
+    pass
diff --git a/tests/test_black.py b/tests/test_black.py
index 23e1e94c41d..a9ea4b9a765 100644
--- a/tests/test_black.py
+++ b/tests/test_black.py
@@ -1523,6 +1523,7 @@ def test_infer_target_version(self) -> None:
                     TargetVersion.PY311,
                     TargetVersion.PY312,
                     TargetVersion.PY313,
+                    TargetVersion.PY314,
                 ],
             ),
             (
@@ -1532,6 +1533,7 @@ def test_infer_target_version(self) -> None:
                     TargetVersion.PY311,
                     TargetVersion.PY312,
                     TargetVersion.PY313,
+                    TargetVersion.PY314,
                 ],
             ),
             ("<3.6", [TargetVersion.PY33, TargetVersion.PY34, TargetVersion.PY35]),
@@ -1543,6 +1545,7 @@ def test_infer_target_version(self) -> None:
                     TargetVersion.PY311,
                     TargetVersion.PY312,
                     TargetVersion.PY313,
+                    TargetVersion.PY314,
                 ],
             ),
             (
@@ -1553,6 +1556,7 @@ def test_infer_target_version(self) -> None:
                     TargetVersion.PY311,
                     TargetVersion.PY312,
                     TargetVersion.PY313,
+                    TargetVersion.PY314,
                 ],
             ),
             (
@@ -1567,6 +1571,7 @@ def test_infer_target_version(self) -> None:
                     TargetVersion.PY311,
                     TargetVersion.PY312,
                     TargetVersion.PY313,
+                    TargetVersion.PY314,
                 ],
             ),
             (
@@ -1583,6 +1588,7 @@ def test_infer_target_version(self) -> None:
                     TargetVersion.PY311,
                     TargetVersion.PY312,
                     TargetVersion.PY313,
+                    TargetVersion.PY314,
                 ],
             ),
             ("==3.8.*", [TargetVersion.PY38]),