Skip to content

Commit

Permalink
Add rule for unnecessary dict() calls (#500)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamchainz committed Apr 13, 2023
1 parent c7e7ffb commit b6c2b95
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Changelog
=========

* Add rule C418 to check for calls passing a dict literal or dict comprehension to ``dict()``.

3.11.1 (2023-03-21)
-------------------

Expand Down
11 changes: 10 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ C409-410: Unnecessary ``<list/tuple>`` passed to ``<list/tuple>``\() - ``<advice
Rules:

* C409 Unnecessary ``<list/tuple>`` passed to tuple() - ``<advice>``.
* C410 Unnecessary ``list passed to list() - ``<advice>``.
* C410 Unnecessary list passed to list() - ``<advice>``.

Where ``<advice>`` is either:

Expand Down Expand Up @@ -207,3 +207,12 @@ For example:
* Rewrite ``list(map(lambda num: num * 2, nums))`` to ``[num * 2 for num in nums]``
* Rewrite ``set(map(lambda num: num % 2 == 0, nums))`` to ``{num % 2 == 0 for num in nums}``
* Rewrite ``dict(map(lambda v: (v, v ** 2), values))`` to ``{v : v ** 2 for v in values}``

C418: Unnecessary ``<dict/dict comprehension>`` passed to dict() - remove the outer call to dict()
--------------------------------------------------------------------------------------------------

It's unnecessary to use a ``dict`` around a dict literal or dict comprehension, since either syntax already constructs a dict.
For example:

* Rewrite ``dict({})`` as ``{}``
* Rewrite ``dict({"a": 1})`` as ``{"a": 1}``
21 changes: 21 additions & 0 deletions src/flake8_comprehensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ def __init__(self, tree: ast.AST) -> None:
"C415": "C415 Unnecessary subscript reversal of iterable within {func}().",
"C416": "C416 Unnecessary {type} comprehension - rewrite using {type}().",
"C417": "C417 Unnecessary use of map - use a {comp} instead.",
"C418": (
"C418 Unnecessary {type} passed to dict() - "
+ "remove the outer call to dict()."
),
}

def run(self) -> Generator[tuple[int, int, str, type[Any]], None, None]:
Expand Down Expand Up @@ -117,6 +121,23 @@ def run(self) -> Generator[tuple[int, int, str, type[Any]], None, None]:
type(self),
)

elif (
num_positional_args == 1
and num_keyword_args == 0
and isinstance(node.args[0], (ast.Dict, ast.DictComp))
and node.func.id == "dict"
):
if isinstance(node.args[0], ast.Dict):
type_ = "dict"
else:
type_ = "dict comprehension"
yield (
node.lineno,
node.col_offset,
self.messages["C418"].format(type=type_),
type(self),
)

elif (
num_positional_args == 1
and isinstance(node.args[0], (ast.Tuple, ast.List))
Expand Down
45 changes: 45 additions & 0 deletions tests/test_flake8_comprehensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -893,3 +893,48 @@ def test_C417_fail(code, failures, flake8_path):
(flake8_path / "example.py").write_text(dedent(code))
result = flake8_path.run_flake8()
assert result.out_lines == failures


@pytest.mark.parametrize(
"code",
[
"dict({}, a=1)",
"dict({x: 1 for x in range(1)}, a=1)",
],
)
def test_C418_pass(code, flake8_path):
(flake8_path / "example.py").write_text(dedent(code))
result = flake8_path.run_flake8()
assert result.out_lines == []


@pytest.mark.parametrize(
"code,failures",
[
(
"dict({})",
[
"./example.py:1:1: C418 Unnecessary dict passed to dict() - "
+ "remove the outer call to dict()."
],
),
(
"dict({'a': 1})",
[
"./example.py:1:1: C418 Unnecessary dict passed to dict() - "
+ "remove the outer call to dict()."
],
),
(
"dict({'x': 1 for x in range(10)})",
[
"./example.py:1:1: C418 Unnecessary dict comprehension passed "
+ "to dict() - remove the outer call to dict()."
],
),
],
)
def test_C418_fail(code, failures, flake8_path):
(flake8_path / "example.py").write_text(dedent(code))
result = flake8_path.run_flake8()
assert result.out_lines == failures

0 comments on commit b6c2b95

Please sign in to comment.