Repository: asottile/pyupgrade

Issue:
multi-line exception with duplicate BaseException and comments gives invalid code
## before
```python          
try:
    ...
except (
    BaseException,
    BaseException # b
):
    ...
```
## after
```python               
try:
    ...
except (BaseException, BaseException # b):
    ...
```
## info
* flags: none (and any I tried seemed to make no difference)
* version: 3.15.0
* real-life code (although it's an intentionally "bad" test file): https://github.com/Zac-HD/flake8-trio/blob/main/tests/eval_files/trio103.py#L195

I expect you never intend to output invalid code, and similar errors could probably pop up in normal usage. I don't have much of an opinion on whether pyupgrade does nothing, or actually manages to remove the `BaseException` and move the comment somewhere else.

Discussion:

Patch:
diff --git a/pyupgrade/_token_helpers.py b/pyupgrade/_token_helpers.py
index 5936ab7..e932c04 100644
--- a/pyupgrade/_token_helpers.py
+++ b/pyupgrade/_token_helpers.py
@@ -369,6 +369,14 @@ def arg_str(tokens: list[Token], start: int, end: int) -> str:
     return tokens_to_src(tokens[start:end]).strip()
 
 
+def _arg_str_no_comment(tokens: list[Token], start: int, end: int) -> str:
+    arg_tokens = [
+        token for token in tokens[start:end]
+        if token.name != 'COMMENT'
+    ]
+    return tokens_to_src(arg_tokens).strip()
+
+
 def _arg_contains_newline(tokens: list[Token], start: int, end: int) -> bool:
     while tokens[start].name in {'NL', 'NEWLINE', UNIMPORTANT_WS}:
         start += 1
@@ -473,7 +481,7 @@ def replace_argument(
 def constant_fold_tuple(i: int, tokens: list[Token]) -> None:
     start = find_op(tokens, i, '(')
     func_args, end = parse_call_args(tokens, start)
-    arg_strs = [arg_str(tokens, *arg) for arg in func_args]
+    arg_strs = [_arg_str_no_comment(tokens, *arg) for arg in func_args]
 
     unique_args = tuple(dict.fromkeys(arg_strs))
 


Test patch:
diff --git a/tests/features/exceptions_test.py b/tests/features/exceptions_test.py
index e855bf2..335e1d4 100644
--- a/tests/features/exceptions_test.py
+++ b/tests/features/exceptions_test.py
@@ -225,6 +225,30 @@ def test_fix_exceptions_version_specific_noop(s, version):
 
             id='leave unrelated error names alone',
         ),
+        pytest.param(
+            'try: ...\n'
+            'except (\n'
+            '    BaseException,\n'
+            '    BaseException # b\n'
+            '): ...\n',
+
+            'try: ...\n'
+            'except BaseException: ...\n',
+
+            id='dedupe with comment.  see #932',
+        ),
+        pytest.param(
+            'try: ...\n'
+            'except (\n'
+            '    A, A,\n'
+            '    B  # b\n'
+            '): ...\n',
+
+            'try: ...\n'
+            'except (A, B): ...\n',
+
+            id='dedupe other exception, one contains comment.  see #932',
+        ),
     ),
 )
 def test_fix_exceptions(s, expected):