Skip to content

Conversation

@mikeleppane
Copy link
Contributor

@mikeleppane mikeleppane commented Aug 7, 2025

Summary

Extends the used-dummy-variable rule (RUF052) to detect dummy variables that are used within list comprehensions, dict comprehensions, set comprehensions, and generator expressions, not just regular for loops and function assignments.

Problem

Previously, RUF052 only flagged dummy variables (variables with leading underscores) that were used in function scopes via assignments or regular for loops. It missed cases where dummy variables were used within comprehensions:

def example():
    my_list = [{"foo": 1}, {"foo": 2}]
    
    # These were not detected before:
    [_item["foo"] for _item in my_list]  # Should warn: _item is used
    {_item["key"]: _item["val"] for _item in my_list}  # Should warn: _item is used
    (_item["foo"] for _item in my_list)  # Should warn: _item is used

Solution

  • Extended scope checking to include all generator scopes () with any (list/dict/set comprehensions and generator expressions) ScopeKind::Generator``GeneratorKind
  • Added support for bindings, which cover loop variables in both regular for loops and comprehensions BindingKind::LoopVar
  • Refactored the scope validation logic for better readability with a descriptive variable is_allowed_scope

ISSUE

Test Plan

cargo test

@github-actions
Copy link
Contributor

github-actions bot commented Aug 7, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+188 -3 violations, +0 -0 fixes in 17 projects; 38 projects unchanged)

DisnakeDev/disnake (+1 -0 violations, +0 -0 fixes)

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

+ disnake/guild.py:5488:29: RUF052 Local dummy variable `_id` is accessed

RasaHQ/rasa (+15 -0 violations, +0 -0 fixes)

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

+ rasa/engine/validation.py:566:13: RUF052 Local dummy variable `_type` is accessed
+ rasa/nlu/extractors/crf_entity_extractor.py:347:29: RUF052 Local dummy variable `_tag` is accessed
+ rasa/nlu/extractors/crf_entity_extractor.py:347:35: RUF052 Local dummy variable `_confidence` is accessed
+ rasa/nlu/utils/bilou_utils.py:82:17: RUF052 Local dummy variable `_tag` is accessed
+ rasa/shared/core/domain.py:618:21: RUF052 Local dummy variable `_entity` is accessed
+ rasa/utils/tensorflow/model_data.py:550:33: RUF052 Local dummy variable `_features` is accessed
+ rasa/utils/tensorflow/model_data_utils.py:249:9: RUF052 Local dummy variable `_features` is accessed
+ rasa/utils/tensorflow/model_data_utils.py:323:9: RUF052 Local dummy variable `_features` is accessed
+ tests/core/test_exporter.py:100:13: RUF052 Local dummy variable `_id` is accessed
+ tests/core/test_exporter.py:103:9: RUF052 Local dummy variable `_id` is accessed
... 5 additional changes omitted for project

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

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

+ airflow-core/src/airflow/jobs/scheduler_job_runner.py:2154:61: RUF052 Local dummy variable `_ti` is accessed
+ airflow-core/src/airflow/jobs/scheduler_job_runner.py:2838:21: RUF052 Local dummy variable `_executor` is accessed
+ airflow-core/src/airflow/jobs/scheduler_job_runner.py:2844:17: RUF052 Local dummy variable `_executor` is accessed
+ airflow-core/src/airflow/serialization/serialized_objects.py:3901:25: RUF052 Local dummy variable `_type` is accessed
+ dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py:698:32: RUF052 Local dummy variable `_python` is accessed
+ dev/breeze/src/airflow_breeze/utils/run_tests.py:542:9: RUF052 Local dummy variable `_test_type` is accessed
+ devel-common/src/tests_common/pytest_plugin.py:227:35: RUF052 Local dummy variable `_fixture` is accessed
+ providers/amazon/tests/system/amazon/aws/utils/__init__.py:299:49: RUF052 Local dummy variable `_task` is accessed
+ providers/amazon/tests/unit/amazon/aws/hooks/test_ecs.py:32:48: RUF052 Local dummy variable `_conn` is accessed
+ providers/amazon/tests/unit/amazon/aws/operators/test_bedrock.py:192:56: RUF052 Local dummy variable `_conn` is accessed
+ providers/amazon/tests/unit/amazon/aws/operators/test_bedrock.py:241:61: RUF052 Local dummy variable `_conn` is accessed
... 32 additional changes omitted for project

apache/superset (+2 -0 violations, +0 -0 fixes)

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

+ superset/commands/report/create.py:151:20: RUF052 Local dummy variable `_invalid_tab_ids` is accessed
+ superset/utils/core.py:1931:9: RUF052 Local dummy variable `_col` is accessed

binary-husky/gpt_academic (+2 -0 violations, +0 -0 fixes)

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

+ crazy_functions/rag_fns/llama_index_worker.py:84:69: RUF052 Local dummy variable `_id` is accessed
+ crazy_functions/rag_fns/milvus_worker.py:99:73: RUF052 Local dummy variable `_id` is accessed

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

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

+ tests/test_examples.py:218:13: RUF052 Local dummy variable `_line` is accessed
+ tests/unit/bokeh/plotting/test_figure.py:195:16: RUF052 Local dummy variable `_type` is accessed

langchain-ai/langchain (+30 -3 violations, +0 -0 fixes)

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

+ libs/core/langchain_core/messages/block_translators/openai.py:351:13: RUF052 Local dummy variable `_id` is accessed
+ libs/core/langchain_core/messages/block_translators/openai.py:364:17: RUF052 Local dummy variable `_id` is accessed
+ libs/core/langchain_core/prompts/chat.py:957:13: RUF052 Local dummy variable `_message` is accessed
+ libs/core/langchain_core/prompts/loading.py:181:5: DOC201 `return` is not documented in docstring
- libs/core/langchain_core/prompts/loading.py:181:5: DOC201 `return` is not documented in docstring
+ libs/core/langchain_core/prompts/loading.py:87:8: RUF052 Local dummy variable `_config` is accessed
+ libs/core/langchain_core/vectorstores/in_memory.py:179:17: RUF052 Local dummy variable `_id` is accessed
+ libs/langchain/langchain_classic/chains/query_constructor/base.py:188:13: RUF052 Local dummy variable `_input` is accessed
... 22 additional changes omitted for rule RUF052
+ libs/langchain/langchain_classic/retrievers/document_compressors/base.py:59:9: DOC201 `return` is not documented in docstring
- libs/langchain/langchain_classic/retrievers/document_compressors/base.py:59:9: DOC201 `return` is not documented in docstring
... 23 additional changes omitted for project

lnbits/lnbits (+2 -0 violations, +0 -0 fixes)

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

+ lnbits/core/services/lnurl.py:132:13: RUF052 Local dummy variable `_data` is accessed
+ lnbits/core/services/websockets.py:39:21: RUF052 Local dummy variable `_conn` is accessed

pandas-dev/pandas (+25 -0 violations, +0 -0 fixes)

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

+ pandas/core/frame.py:8350:21: RUF052 Local dummy variable `_left` is accessed
+ pandas/core/frame.py:8350:28: RUF052 Local dummy variable `_right` is accessed
+ pandas/core/groupby/groupby.py:2629:26: RUF052 Local dummy variable `_name` is accessed
+ pandas/core/indexing.py:2735:44: RUF052 Local dummy variable `_i` is accessed
+ pandas/core/indexing.py:2735:48: RUF052 Local dummy variable `_idx` is accessed
+ pandas/core/internals/managers.py:2429:10: RUF052 Local dummy variable `_can_consolidate` is accessed
+ pandas/io/parsers/python_parser.py:1200:20: RUF052 Local dummy variable `_content` is accessed
+ pandas/plotting/_matplotlib/core.py:2203:17: RUF052 Local dummy variable `_patch` is accessed
+ pandas/plotting/_matplotlib/core.py:2203:25: RUF052 Local dummy variable `_leglabel` is accessed
+ pandas/tests/frame/methods/test_to_dict_of_blocks.py:17:13: RUF052 Local dummy variable `_df` is accessed
... 15 additional changes omitted for project

python-poetry/poetry (+2 -0 violations, +0 -0 fixes)

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

+ src/poetry/console/commands/add.py:166:13: RUF052 Local dummy variable `_constraint` is accessed
+ src/poetry/puzzle/solver.py:170:21: RUF052 Local dummy variable `_package` is accessed

reflex-dev/reflex (+4 -0 violations, +0 -0 fixes)

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

+ reflex/compiler/utils.py:76:14: RUF052 Local dummy variable `_imports` is accessed
+ reflex/compiler/utils.py:77:13: RUF052 Local dummy variable `_import` is accessed
+ reflex/components/el/__init__.py:12:30: RUF052 Local dummy variable `_v` is accessed
+ reflex/components/markdown/markdown.py:497:13: RUF052 Local dummy variable `_component` is accessed

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

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

+ rotkehlchen/api/rest.py:2893:72: RUF052 Local dummy variable `_chain` is accessed
+ rotkehlchen/api/v1/schemas.py:702:21: RUF052 Local dummy variable `_type` is accessed
+ rotkehlchen/chain/ethereum/modules/liquity/trove.py:241:38: RUF052 Local dummy variable `_asset` is accessed
+ rotkehlchen/chain/ethereum/modules/liquity/trove.py:241:46: RUF052 Local dummy variable `_key` is accessed
+ rotkehlchen/chain/ethereum/modules/sushiswap/decoder.py:132:13: RUF052 Local dummy variable `_tx_log` is accessed
+ rotkehlchen/chain/evm/decoding/aave/v3/decoder.py:258:13: RUF052 Local dummy variable `_log` is accessed
+ rotkehlchen/chain/evm/decoding/curve/decoder.py:243:21: RUF052 Local dummy variable `_log` is accessed
+ rotkehlchen/chain/evm/decoding/curve/decoder.py:340:25: RUF052 Local dummy variable `_log` is accessed
+ rotkehlchen/chain/evm/decoding/curve/decoder.py:381:25: RUF052 Local dummy variable `_log` is accessed
+ rotkehlchen/chain/evm/decoding/curve/decoder.py:406:17: RUF052 Local dummy variable `_log` is accessed
... 5 additional changes omitted for project

scikit-build/scikit-build (+2 -0 violations, +0 -0 fixes)

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

+ skbuild/cmaker.py:653:55: RUF052 Local dummy variable `_install` is accessed
+ tests/test_setup.py:808:9: RUF052 Local dummy variable `_type` is accessed

python-trio/trio (+4 -0 violations, +0 -0 fixes)

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

+ src/trio/_core/_tests/test_asyncgen.py:230:9: RUF052 Local dummy variable `_attempt` is accessed
+ src/trio/_tests/test_path.py:213:19: RUF052 Local dummy variable `_results` is accessed
+ src/trio/_tests/test_path.py:213:9: RUF052 Local dummy variable `_pattern` is accessed
+ src/trio/socket.py:30:13: RUF052 Local dummy variable `_name` is accessed

wntrblm/nox (+2 -0 violations, +0 -0 fixes)

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

+ tests/test_logger.py:26:74: RUF052 Local dummy variable `_log` is accessed
+ tests/test_logger.py:32:74: RUF052 Local dummy variable `_log` is accessed

... Truncated remaining completed project reports due to GitHub comment length restrictions

Changes by rule (3 rules affected)

code total + violation - violation + fix - fix
RUF052 185 185 0 0 0
DOC201 4 2 2 0 0
DOC501 2 1 1 0 0

@mikeleppane
Copy link
Contributor Author

Hmm...It seems that a significant number of changes have been detected in the ecosystem run.

@ntBre ntBre self-requested a review August 8, 2025 14:06
@ntBre ntBre linked an issue Aug 13, 2025 that may be closed by this pull request
@ntBre ntBre added the rule Implementing or modifying a lint rule label Aug 13, 2025
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, the code changes look reasonable to me! If you don't mind, can you reorganize the snapshots a bit as I suggested down below? That will make it a little easier for me to review those. Otherwise I just had some minor simplification suggestions.

As a note for myself, we don't have to do any special preview-gating here because the rule itself is still in preview.

Have you had a chance to go through the ecosystem changes? It's not necessarily a bad thing for there to be this many of them, if they're all correct. I'll take a look at them at some point too, but I wanted to see if you've had the first pass yet.

@MichaReiser
Copy link
Member

@ntBre it seems like there were only a few nit comments. Would it make sense for you to address them to get this PR landed?

@ntBre ntBre added the preview Related to preview mode features label Nov 21, 2025
Comment on lines +130 to 154
match binding.kind {
BindingKind::Annotation
| BindingKind::Argument
| BindingKind::NamedExprAssignment
| BindingKind::Assignment
| BindingKind::LoopVar
| BindingKind::WithItemVar
| BindingKind::BoundException
| BindingKind::UnboundException(_) => {}

BindingKind::TypeParam
| BindingKind::Global(_)
| BindingKind::Nonlocal(_, _)
| BindingKind::Builtin
| BindingKind::ClassDefinition(_)
| BindingKind::FunctionDefinition(_)
| BindingKind::Export(_)
| BindingKind::FutureImport
| BindingKind::Import(_)
| BindingKind::FromImport(_)
| BindingKind::SubmoduleImport(_)
| BindingKind::Deletion
| BindingKind::ConditionalDeletion(_)
| BindingKind::DunderClassCell => return,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I went ahead and expanded this a bit more to all of the binding kinds that seemed like "local" variables instead of only adding LoopVar. And we now match exhaustively so we know we've considered all of them.

Comment on lines +205 to +213
RUF052 Local dummy variable `other` is accessed
--> RUF052_0.py:177:13
|
175 | return
176 | _seen.add(self)
177 | for other in self.connected:
| ^^^^^
178 | other.recurse(_seen=_seen)
|
Copy link
Contributor

Choose a reason for hiding this comment

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

I was pretty confused by this at first, but the dummy-variable-rgx setting for this test is the empty string:

#[test_case(Rule::UsedDummyVariable, Path::new("RUF052_0.py"), r"", 2)]

so I think it's correct. We just weren't checking usage of dummy loop variables before.

@ntBre ntBre changed the title [ruff] Extend RUF052 to handle dummy variables in comprehensions an… [ruff] Catch more dummy variable uses (RUF052) Nov 21, 2025
@ntBre
Copy link
Contributor

ntBre commented Nov 21, 2025

I checked most of the ecosystem hits (I checked all of the error messages in the comment, but only clicked through to most), and they look correct to me. I think the DOC changes must be unrelated since we only modify the RUF052 code in this PR, maybe some quirk of the base branch or an old PR.

@ntBre ntBre merged commit e2a1d1a into astral-sh:main Nov 21, 2025
37 checks passed
dcreager added a commit that referenced this pull request Nov 24, 2025
…d-typevar

* origin/main: (24 commits)
  [ty] Remove brittle constraint set reveal tests (#21568)
  [`ruff`] Catch more dummy variable uses (`RUF052`) (#19799)
  [ty] Use the same snapshot handling as other tests (#21564)
  [ty] suppress autocomplete suggestions during variable binding (#21549)
  Set severity for non-rule diagnostics (#21559)
  [ty] Add `with_type` convenience to display code (#21563)
  [ty] Implement docstring rendering to markdown (#21550)
  [ty] Reduce indentation of `TypeInferenceBuilder::infer_attribute_load` (#21560)
  Bump 0.14.6 (#21558)
  [ty] Improve debug messages when imports fail (#21555)
  [ty] Add support for relative import completions
  [ty] Refactor detection of import statements for completions
  [ty] Use dedicated collector for completions
  [ty] Attach subdiagnostics to `unresolved-import` errors for relative imports as well as absolute imports (#21554)
  [ty] support PEP 613 type aliases (#21394)
  [ty] More low-hanging fruit for inlay hint goto-definition (#21548)
  [ty] implement `TypedDict` structural assignment (#21467)
  [ty] Add more random TypeDetails and tests (#21546)
  [ty] Add goto for `Unknown` when it appears in an inlay hint (#21545)
  [ty] Add type definitions for `Type::SpecialForm`s (#21544)
  ...
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.

RUF052: Use of dummy variables is not flagged for loop variables

3 participants