Skip to content

[flake8-comprehensions] Document RecursionError edge case in __len__ (C416)#25286

Merged
ntBre merged 2 commits into
astral-sh:mainfrom
adityasingh2400:docs-c416-recursion-error
May 21, 2026
Merged

[flake8-comprehensions] Document RecursionError edge case in __len__ (C416)#25286
ntBre merged 2 commits into
astral-sh:mainfrom
adityasingh2400:docs-c416-recursion-error

Conversation

@adityasingh2400
Copy link
Copy Markdown
Contributor

@adityasingh2400 adityasingh2400 commented May 21, 2026

C416's autofix rewrites a list comprehension like [item for item in self] into list(self), but when the original is reached from inside __len__, the rewrite recurses: CPython's list() constructor probes __length_hint__ (which dispatches to __len__) as a sizing optimization while iterating, so list(self) from inside __len__ calls __len__ again. The same applies to dict() and set() calls on self.

In #13752, MichaReiser noted that extending the docs would be reasonable and that detecting this control flow isn't the goal. I added a paragraph to the C416 docstring with a runnable example of the failure mode, plus the # noqa: C416 workaround for when the comprehension is reached (directly or transitively) from __len__.

The change is docstring-only. cargo dev generate-all and uvx prek run --from-ref main both pass.

fixes #13752

… `__len__`

Reaching `[item for item in self]` (and the dict/set equivalents) from
inside `__len__` is safe today, but the C416 auto-fix replaces it with
`list(self)`, which CPython calls back into `__len__` via the length
hint optimization, recursing infinitely.

The rule's heuristic intentionally doesn't try to detect this control
flow. Document the failure mode in the rule's "Known problems" so users
can suppress with `# noqa: C416` rather than discovering it at runtime.

Closes astral-sh#13752.
@astral-sh-bot astral-sh-bot Bot requested a review from ntBre May 21, 2026 11:16
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot Bot commented May 21, 2026

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@ntBre ntBre changed the title [flake8-comprehensions] Document C416 RecursionError edge case in __len__ [flake8-comprehensions] DocumentRecursionError edge case in __len__ (C416) May 21, 2026
@ntBre ntBre changed the title [flake8-comprehensions] DocumentRecursionError edge case in __len__ (C416) [flake8-comprehensions] Document RecursionError edge case in __len__ (C416) May 21, 2026
@ntBre ntBre added the documentation Improvements or additions to documentation label May 21, 2026
@ntBre ntBre enabled auto-merge (squash) May 21, 2026 20:32
@ntBre ntBre merged commit ba87ab5 into astral-sh:main May 21, 2026
44 checks passed
thejchap pushed a commit to thejchap/ruff that referenced this pull request May 23, 2026
…en__` (`C416`) (astral-sh#25286)

C416's autofix rewrites a list comprehension like `[item for item in
self]` into `list(self)`, but when the original is reached from inside
`__len__`, the rewrite recurses: CPython's `list()` constructor probes
`__length_hint__` (which dispatches to `__len__`) as a sizing
optimization while iterating, so `list(self)` from inside `__len__`
calls `__len__` again. The same applies to `dict()` and `set()` calls on
`self`.

In astral-sh#13752, MichaReiser noted that extending the docs would be reasonable
and that detecting this control flow isn't the goal. I added a paragraph
to the C416 docstring with a runnable example of the failure mode, plus
the `# noqa: C416` workaround for when the comprehension is reached
(directly or transitively) from `__len__`.

The change is docstring-only. `cargo dev generate-all` and `uvx prek run
--from-ref main` both pass.

fixes astral-sh#13752

---------

Co-authored-by: Brent Westbrook <[email protected]>
anishgirianish pushed a commit to anishgirianish/ruff that referenced this pull request May 28, 2026
…en__` (`C416`) (astral-sh#25286)

C416's autofix rewrites a list comprehension like `[item for item in
self]` into `list(self)`, but when the original is reached from inside
`__len__`, the rewrite recurses: CPython's `list()` constructor probes
`__length_hint__` (which dispatches to `__len__`) as a sizing
optimization while iterating, so `list(self)` from inside `__len__`
calls `__len__` again. The same applies to `dict()` and `set()` calls on
`self`.

In astral-sh#13752, MichaReiser noted that extending the docs would be reasonable
and that detecting this control flow isn't the goal. I added a paragraph
to the C416 docstring with a runnable example of the failure mode, plus
the `# noqa: C416` workaround for when the comprehension is reached
(directly or transitively) from `__len__`.

The change is docstring-only. `cargo dev generate-all` and `uvx prek run
--from-ref main` both pass.

fixes astral-sh#13752

---------

Co-authored-by: Brent Westbrook <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[C416 unnecessary-comprehension] Suggests a fix leading to RecursionError

2 participants