Skip to content

Conversation

@naslundx
Copy link
Contributor

@naslundx naslundx commented May 9, 2025

Summary

The existing implementation of RUF060 (InEmptyCollection) is not recursive, meaning that although set([]) results in an empty collection, the existing code fails it because set is taking an argument.

The updated implementation allows set and frozenset to take empty collection as positional argument (which results in empty set/frozenset).

Test Plan

Added test cases for recursive cases + updated snapshot (see RUF060.py).

@github-actions
Copy link
Contributor

github-actions bot commented May 9, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

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!

Comment on lines 74 to 78
Expr::Set(ast::ExprSet { elts, .. }) => match elts.as_slice() {
[] => true,
[element] => is_empty(element, semantic),
_ => false,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, is this part correct? I tested locally and

frozenset() in {frozenset()}

is getting a diagnostic now, even though Python returns True:

>>> frozenset() in {frozenset()}
True

I think we may need to limit the recursive case to calls like down below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, that's a very good find. Indeed, I did not consider the difference between how the two ways of constructing a set differ

>>> set([frozenset()]) == {frozenset()}  # note the [...]
True

Will fix

Copy link
Contributor Author

@naslundx naslundx May 9, 2025

Choose a reason for hiding this comment

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

Did not expect this either, but...

>>> len({""})
1

>>> len(set(""))
0

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah that is a funny one, calling set on a string will create a set of the characters:

>>> set("123")
{'1', '2', '3'}

so the empty string gives the empty set.

Copy link
Contributor

Choose a reason for hiding this comment

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

Classic str is an Iterable[str] ^-^

@naslundx
Copy link
Contributor Author

naslundx commented May 9, 2025

Thanks @ntBre - I reduced the PR but added some more test cases from comments above. (changes in new commit)

@dylwil3 dylwil3 added rule Implementing or modifying a lint rule preview Related to preview mode features labels May 11, 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!

@ntBre ntBre merged commit b2d9f59 into astral-sh:main May 12, 2025
34 checks passed
dcreager added a commit that referenced this pull request May 13, 2025
…eepish

* origin/main:
  [ty] Induct into instances and subclasses when finding and applying generics (#18052)
  [ty] Allow classes to inherit from `type[Any]` or `type[Unknown]` (#18060)
  [ty] Allow a class to inherit from an intersection if the intersection contains a dynamic type and the intersection is not disjoint from `type` (#18055)
  [ty] Narrowing for `hasattr()` (#18053)
  Update reference documentation for `--python-version` (#18056)
  [`flake8-bugbear`] Ignore `B028` if `skip_file_prefixes` is present (#18047)
  [`airflow`] Apply try-catch guard to all AIR3 rules (`AIR3`) (#17887)
  [`pylint`] add fix safety section (`PLW3301`) (#17878)
  Update `--python` to accept paths to executables in virtual environments (#17954)
  [`pylint`] add fix safety section (`PLE4703`) (#17824)
  [`ruff`] Implement a recursive check for `RUF060` (#17976)
  [`flake8-use-pathlib`] `PTH*` suppress diagnostic for all `os.*` functions that have the `dir_fd` parameter (#17968)
  [`refurb`] Mark autofix as safe only for number literals in `FURB116` (#17692)
  [`flake8-simplify`] Fix `SIM905` autofix for `rsplit` creating a reversed list literal (#18045)
  Avoid initializing progress bars early (#18049)
dcreager added a commit that referenced this pull request May 13, 2025
…eep-dish

* origin/main:
  [ty] Infer parameter specializations of generic aliases (#18021)
  [ty] Understand homogeneous tuple annotations (#17998)
  [ty] Induct into instances and subclasses when finding and applying generics (#18052)
  [ty] Allow classes to inherit from `type[Any]` or `type[Unknown]` (#18060)
  [ty] Allow a class to inherit from an intersection if the intersection contains a dynamic type and the intersection is not disjoint from `type` (#18055)
  [ty] Narrowing for `hasattr()` (#18053)
  Update reference documentation for `--python-version` (#18056)
  [`flake8-bugbear`] Ignore `B028` if `skip_file_prefixes` is present (#18047)
  [`airflow`] Apply try-catch guard to all AIR3 rules (`AIR3`) (#17887)
  [`pylint`] add fix safety section (`PLW3301`) (#17878)
  Update `--python` to accept paths to executables in virtual environments (#17954)
  [`pylint`] add fix safety section (`PLE4703`) (#17824)
  [`ruff`] Implement a recursive check for `RUF060` (#17976)
  [`flake8-use-pathlib`] `PTH*` suppress diagnostic for all `os.*` functions that have the `dir_fd` parameter (#17968)
  [`refurb`] Mark autofix as safe only for number literals in `FURB116` (#17692)
  [`flake8-simplify`] Fix `SIM905` autofix for `rsplit` creating a reversed list literal (#18045)
  Avoid initializing progress bars early (#18049)
Glyphack pushed a commit to Glyphack/ruff that referenced this pull request May 21, 2025
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
The existing implementation of RUF060 (InEmptyCollection) is not
recursive, meaning that although set([]) results in an empty collection,
the existing code fails it because set is taking an argument.

The updated implementation allows set and frozenset to take empty
collection as positional argument (which results in empty
set/frozenset).

## Test Plan

Added test cases for recursive cases + updated snapshot (see RUF060.py).

---------

Co-authored-by: Marcus Näslund <marcus.naslund@kognity.com>
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.

4 participants