Skip to content

[perflint] Extend PERF102 to comprehensions and generators#23473

Merged
ntBre merged 7 commits intoastral-sh:mainfrom
anishgirianish:extend-perf102-to-comprehensions
Mar 3, 2026
Merged

[perflint] Extend PERF102 to comprehensions and generators#23473
ntBre merged 7 commits intoastral-sh:mainfrom
anishgirianish:extend-perf102-to-comprehensions

Conversation

@anishgirianish
Copy link
Contributor

@anishgirianish anishgirianish commented Feb 21, 2026

Summary

Extends PERF102 to catch .items() misuse in comprehensions and generators, not just for loops.

Extracted the core detection into a shared helper (check_dict_items_usage) so both the for loop and comprehension paths can reuse it. The comprehension check runs between Step 2 and Step 3 in visit_expr since is_unused() needs the generator scope to still be active.

now flagged
_ = [k for k, _ in d.items()] # use .keys()
_ = {v for _, v in d.items()} # use .values()
_ = (v for _, v in d.items()) # use .values()

still fine
_ = [(k, v) for k, v in d.items()] # both used
_ = [k for k, v in d.items() if v] # v used in condition

Test Plan

  • Added error and no-error cases for list/set/dict comps, generators, and nested generators
  • Tests, clippy, and prek all pass

Closes #6638

@astral-sh-bot
Copy link

astral-sh-bot bot commented Feb 21, 2026

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+39 -0 violations, +0 -0 fixes in 11 projects; 45 projects unchanged)

apache/airflow (+7 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ airflow-core/src/airflow/models/deadline.py:289:40: PERF102 When using only the values of a dict use the `values()` method
+ airflow-core/src/airflow/serialization/definitions/deadline.py:75:40: PERF102 When using only the values of a dict use the `values()` method
+ airflow-core/src/airflow/serialization/serialized_objects.py:1699:75: PERF102 When using only the values of a dict use the `values()` method
+ airflow-core/src/airflow/utils/sqlalchemy.py:189:28: PERF102 When using only the keys of a dict use the `keys()` method
+ providers/databricks/src/airflow/providers/databricks/operators/databricks_workflow.py:179:34: PERF102 When using only the values of a dict use the `values()` method
+ providers/teradata/src/airflow/providers/teradata/hooks/teradata.py:302:57: PERF102 When using only the values of a dict use the `values()` method
+ task-sdk/src/airflow/sdk/definitions/_internal/expandinput.py:267:44: PERF102 When using only the values of a dict use the `values()` method

bokeh/bokeh (+1 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ src/bokeh/core/has_props.py:546:50: PERF102 When using only the keys of a dict use the `keys()` method

ibis-project/ibis (+1 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ ibis/expr/sql.py:263:59: PERF102 When using only the values of a dict use the `values()` method

langchain-ai/langchain (+1 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ libs/core/tests/unit_tests/prompts/test_structured.py:24:57: PERF102 When using only the keys of a dict use the `keys()` method

latchbio/latch (+1 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ src/latch_cli/snakemake/workflow.py:1568:32: PERF102 When using only the keys of a dict use the `keys()` method

qdrant/qdrant-client (+3 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ qdrant_client/local/async_qdrant_local.py:661:34: PERF102 When using only the keys of a dict use the `keys()` method
+ qdrant_client/local/local_collection.py:122:43: PERF102 When using only the keys of a dict use the `keys()` method
+ qdrant_client/local/qdrant_local.py:718:32: PERF102 When using only the keys of a dict use the `keys()` method

rotki/rotki (+4 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ rotkehlchen/api/services/external_services.py:16:84: PERF102 When using only the values of a dict use the `values()` method
+ rotkehlchen/chain/evm/decoding/hop/decoder.py:80:29: PERF102 When using only the values of a dict use the `values()` method
+ rotkehlchen/rotkehlchen.py:1222:58: PERF102 When using only the values of a dict use the `values()` method
+ rotkehlchen/rotkehlchen.py:1223:76: PERF102 When using only the values of a dict use the `values()` method

zulip/zulip (+1 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ zerver/lib/events.py:2283:46: PERF102 When using only the values of a dict use the `values()` method

indico/indico (+5 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ indico/core/permissions.py:122:35: PERF102 [*] When using only the values of a dict use the `values()` method
+ indico/core/permissions.py:236:55: PERF102 [*] When using only the values of a dict use the `values()` method
+ indico/modules/events/layout/util.py:190:34: PERF102 [*] When using only the values of a dict use the `values()` method
+ indico/modules/events/layout/util.py:269:31: PERF102 [*] When using only the values of a dict use the `values()` method
+ indico/modules/vc/models/vc_rooms.py:43:73: PERF102 [*] When using only the values of a dict use the `values()` method

pdm-project/pdm (+1 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ src/pdm/installers/base.py:209:28: PERF102 When using only the keys of a dict use the `keys()` method

home-assistant/core (+14 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ homeassistant/components/apcupsd/sensor.py:474:60: PERF102 When using only the keys of a dict use the `keys()` method
+ homeassistant/components/blink/diagnostics.py:25:26: PERF102 When using only the values of a dict use the `values()` method
+ homeassistant/components/canary/alarm_control_panel.py:31:38: PERF102 When using only the values of a dict use the `values()` method
+ homeassistant/components/fritz/diagnostics.py:48:34: PERF102 When using only the values of a dict use the `values()` method
+ homeassistant/components/hvv_departures/binary_sensor.py:123:27: PERF102 When using only the keys of a dict use the `keys()` method
+ homeassistant/components/insteon/api/aldb.py:66:67: PERF102 When using only the values of a dict use the `values()` method
+ homeassistant/components/rympro/sensor.py:71:32: PERF102 When using only the keys of a dict use the `keys()` method
+ homeassistant/components/schluter/climate.py:74:42: PERF102 When using only the keys of a dict use the `keys()` method
+ homeassistant/components/seventeentrack/sensor.py:28:37: PERF102 When using only the keys of a dict use the `keys()` method
+ homeassistant/components/vodafone_station/diagnostics.py:42:39: PERF102 When using only the values of a dict use the `values()` method
+ homeassistant/components/weatherflow_cloud/weather.py:38:37: PERF102 When using only the keys of a dict use the `keys()` method
+ script/hassfest/config_flow.py:150:36: PERF102 When using only the keys of a dict use the `keys()` method
+ tests/components/keenetic_ndms2/test_config_flow.py:241:21: PERF102 When using only the keys of a dict use the `keys()` method
+ tests/components/kostal_plenticore/test_switch.py:107:24: PERF102 When using only the keys of a dict use the `keys()` method

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
PERF102 39 39 0 0 0

@ntBre ntBre self-assigned this Feb 27, 2026
Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This looks reasonable to me overall, I mostly just had a couple of nits and then a larger suggestion about where to put this check.

We should also make this a preview change since it's a significant expansion to a stable rule.

@ntBre ntBre added rule Implementing or modifying a lint rule preview Related to preview mode features labels Feb 27, 2026
ntBre added a commit that referenced this pull request Feb 27, 2026
Summary
--

While reviewing #23473, I noticed that the `while` loops in our deferred checks
for `for` loops and lambdas would only be used if we pushed additional deferred
scopes to the `Checker` while running lint rule code. This should actually be
impossible given that the rules only receive a `&Checker` (as shown by the
immutable re-borrow), and none of our tests fail when removing the loops.

I guess it doesn't hurt to have the loops either since they only run once, but
it did serve to confuse me today.

I assume, but didn't fully confirm, that this used to be interleaved with the
visiting phase, at which point additional deferred scopes could have still been
added in the middle of this.

Test Plan
--

Existing tests
@amyreese
Copy link
Member

I really like the ecosystem hits here, didn't realize how common it was to use items() without using both key and value. 😅

That said, when giving advice to use keys(), would it be better to simply recommend iterating over the dictionary itself (which defaults to iterating over keys)? Or does PERF102 already suggest keys() for a specific reason?

@ntBre
Copy link
Contributor

ntBre commented Feb 28, 2026

That said, when giving advice to use keys(), would it be better to simply recommend iterating over the dictionary itself (which defaults to iterating over keys)? Or does PERF102 already suggest keys() for a specific reason?

Let's not change too much about PERF102 in this PR 😄 I don't know the full history of recommending keys, but I'm happy to reuse the rule's normal fix for loops here.

@anishgirianish anishgirianish requested a review from ntBre March 2, 2026 14:58
Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the ecosystem check, we still need to preview gate this change.

@ntBre ntBre changed the title [perflint] Extend PERF102 to comprehensions and generators [perflint] Extend PERF102 to comprehensions and generators Mar 2, 2026
@anishgirianish anishgirianish requested a review from ntBre March 3, 2026 00:43
@anishgirianish
Copy link
Contributor Author

@ntBre Addressed all feedback with latest commits:

  • Preview gated comprehension checks.
  • Removed &'a Comprehension from Analyze.

Would like to request you for your review whenever you get a chance. Thank you

Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! This makes sense to me.

I'll just apply my one suggestion adding a debug assertion to make it slightly more like the other deferred checks.

@anishgirianish
Copy link
Contributor Author

Thank you! This makes sense to me.

I'll just apply my one suggestion adding a debug assertion to make it slightly more like the other deferred checks.

Thank you very much for the latest push. I really appreciate it.

@ntBre ntBre merged commit c7db398 into astral-sh:main Mar 3, 2026
43 checks passed
carljm added a commit that referenced this pull request Mar 3, 2026
* main:
  [ty] Apply narrowing to walrus values (#23687)
  [`perflint`] Extend `PERF102` to comprehensions and generators (#23473)
  [ty] Fix GitHub-annotations mdtest output format (#23694)
  [ty] Reduce the number of potentially-flaky projects (#23698)
  [`pydocstyle`] Fix numpy section ordering (`D420`) (#23685)
  [ty] Move method-related types to a submodule (#23691)
  [ty] Avoid the mandatory "ecosystem-analyzer workflow run cancelled" notification every time you make a PR (#23695)
  [ty] Move `Type::subtyping_is_always_reflexive` to `types::relation` (#23692)
  Update conformance suite commit hash (#23693)
  [ty] Add mdtest suite for `typing.Concatenate` (#23554)
  [ty] filter out pre-loop bindings from loop headers (#23536)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Related to preview mode features rule Implementing or modifying a lint rule

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PERF102 is not detected in generators or comprehensions

3 participants