Repository: asottile/pyupgrade

Issue:
Incorrect rewrite of six.reraise with traceback as keyword argument
Reduced from real code in Launchpad:

```
$ cat t.py
import sys

from six import reraise

exc_info = sys.exc_info()
reraise(exc_info[0], exc_info[1], tb=exc_info[2])
$ pyupgrade --py3-plus t.py
Rewriting t.py
$ cat t.py
import sys

from six import reraise

exc_info = sys.exc_info()
raise exc_info[1].with_traceback(None)
```

pyupgrade has missed the keyword argument here; this should instead be rewritten to `raise exc_info[1].with_traceback(exc_info[2])`.

(In fact in Python 3 this can end up just being a plain `raise`: the original code was more complicated and had a second possible exception being raised and handled between `sys.exc_info()` and `reraise()`, which in Python 2 would have caused plain `raise` to raise the second exception, but the exception scoping changes in Python 3 mean that a plain `raise` will do the right thing.  I don't expect pyupgrade to do that sort of analysis, though.)

Discussion:

Patch:
diff --git a/pyupgrade/_plugins/six_calls.py b/pyupgrade/_plugins/six_calls.py
index 90c4ee3..ac58fd5 100644
--- a/pyupgrade/_plugins/six_calls.py
+++ b/pyupgrade/_plugins/six_calls.py
@@ -172,13 +172,21 @@ def visit_Call(
                 ('reraise',),
             )
     ):
-        if len(node.args) == 2 and not has_starargs(node):
+        if (
+                len(node.args) == 2 and
+                not node.keywords and
+                not has_starargs(node)
+        ):
             func = functools.partial(
                 find_and_replace_call,
                 template=RERAISE_2_TMPL,
             )
             yield ast_to_offset(node), func
-        elif len(node.args) == 3 and not has_starargs(node):
+        elif (
+                len(node.args) == 3 and
+                not node.keywords and
+                not has_starargs(node)
+        ):
             func = functools.partial(
                 find_and_replace_call,
                 template=RERAISE_3_TMPL,
@@ -186,6 +194,7 @@ def visit_Call(
             yield ast_to_offset(node), func
         elif (
                 len(node.args) == 1 and
+                not node.keywords and
                 isinstance(node.args[0], ast.Starred) and
                 isinstance(node.args[0].value, ast.Call) and
                 is_name_attr(


Test patch:
diff --git a/tests/features/six_test.py b/tests/features/six_test.py
index e1f6cd8..4ea3f5b 100644
--- a/tests/features/six_test.py
+++ b/tests/features/six_test.py
@@ -24,6 +24,7 @@ from pyupgrade._main import _fix_plugins
         'class C(six.with_metaclass(Meta, B), D): pass',
         # cannot determine args to rewrite them
         'six.reraise(*err)', 'six.u(*a)',
+        'six.reraise(a, b, tb=c)',
         'class C(six.with_metaclass(*a)): pass',
         '@six.add_metaclass(*a)\n'
         'class C: pass\n',