diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f9ad2b0..eddb07d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,10 @@ Changelog ========= +* Expand C416 to ``dict`` comprehensions. + + Thanks to Aaron Gokaslan in `PR #490 `__. + 3.10.1 (2022-10-29) ------------------- diff --git a/README.rst b/README.rst index b4391d4..277b333 100644 --- a/README.rst +++ b/README.rst @@ -150,13 +150,14 @@ For example: * Rewrite ``sorted(iterable)[::-1]`` as ``sorted(iterable, reverse=True)`` * Rewrite ``reversed(iterable[::-1])`` as ``iterable`` -C416: Unnecessary ```` comprehension - rewrite using ````\(). ---------------------------------------------------------------------------------- +C416: Unnecessary ```` comprehension - rewrite using ````\(). +------------------------------------------------------------------------------------------- -It's unnecessary to use a list comprehension if the elements are unchanged. -The iterable should be wrapped in ``list()`` or ``set()`` instead. +It's unnecessary to use a dict/list/set comprehension to build a data structure if the elements are unchanged. +Wrap the iterable with ``dict()``, ``list()``, or ``set()`` instead. For example: +* Rewrite ``{a: b for a, b in iterable}`` as ``dict(iterable)`` * Rewrite ``[x for x in iterable]`` as ``list(iterable)`` * Rewrite ``{x for x in iterable}`` as ``set(iterable)`` diff --git a/src/flake8_comprehensions/__init__.py b/src/flake8_comprehensions/__init__.py index 11e5563..9f25550 100644 --- a/src/flake8_comprehensions/__init__.py +++ b/src/flake8_comprehensions/__init__.py @@ -295,15 +295,29 @@ def run(self) -> Generator[tuple[int, int, str, type[Any]], None, None]: type(self), ) - elif isinstance(node, (ast.ListComp, ast.SetComp)): + elif isinstance(node, (ast.DictComp, ast.ListComp, ast.SetComp)): if ( len(node.generators) == 1 and not node.generators[0].ifs and not node.generators[0].is_async and ( - isinstance(node.elt, ast.Name) - and isinstance(node.generators[0].target, ast.Name) - and node.elt.id == node.generators[0].target.id + ( + isinstance(node, (ast.ListComp, ast.SetComp)) + and isinstance(node.elt, ast.Name) + and isinstance(node.generators[0].target, ast.Name) + and node.elt.id == node.generators[0].target.id + ) + or ( + isinstance(node, ast.DictComp) + and isinstance(node.key, ast.Name) + and isinstance(node.value, ast.Name) + and isinstance(node.generators[0].target, ast.Tuple) + and len(node.generators[0].target.elts) == 2 + and isinstance(node.generators[0].target.elts[0], ast.Name) + and node.generators[0].target.elts[0].id == node.key.id + and isinstance(node.generators[0].target.elts[1], ast.Name) + and node.generators[0].target.elts[1].id == node.value.id + ) ) ): yield ( diff --git a/tests/test_flake8_comprehensions.py b/tests/test_flake8_comprehensions.py index 5e9626a..72e5114 100644 --- a/tests/test_flake8_comprehensions.py +++ b/tests/test_flake8_comprehensions.py @@ -757,6 +757,10 @@ def test_C415_fail(code, failures, flake8_path): @pytest.mark.parametrize( "code", [ + "{x, y for x, y, z in zip('abc', '123', 'def')}", + "{y: x for x, y in zip('abc', '123')}", + "{x: y for x, (y,) in zip('a', ('1',))}", + "{x: z for x, (y,), z in zip('a', ('1',), 'b')}", "[str(x) for x in range(5)]", "[x + 1 for x in range(5)]", "[x for x in range(5) if x % 2]", @@ -796,6 +800,20 @@ def test_C416_pass(code, flake8_path): @pytest.mark.parametrize( "code,failures", [ + ( + "{x: y for x, y in zip(range(5), range(5))}", + [ + "./example.py:1:1: C416 Unnecessary dict comprehension - " + + "rewrite using dict().", + ], + ), + ( + "{x: y for (x, y) in zip(range(5), range(5))}", + [ + "./example.py:1:1: C416 Unnecessary dict comprehension - " + + "rewrite using dict().", + ], + ), ( "[x for x in range(5)]", [