Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RUF005 "unpack instead of concatenating" check #1957

Merged
merged 2 commits into from
Jan 19, 2023

Conversation

akx
Copy link
Contributor

@akx akx commented Jan 18, 2023

This PR adds a new check that turns expressions such as [1, 2, 3] + foo into [1, 2, 3, *foo], since the latter is easier to read and faster:

~ $ python3.11 -m timeit -s 'b = [6, 5, 4]' '[1, 2, 3] + b'
5000000 loops, best of 5: 81.4 nsec per loop
~ $ python3.11 -m timeit -s 'b = [6, 5, 4]' '[1, 2, 3, *b]'
5000000 loops, best of 5: 66.2 nsec per loop

However there's a couple of gotchas:

  • This felt like a simplify rule, so I borrowed an unused SIM code even if the upstream flake8-simplify doesn't do this transform. If it should be assigned some other code, let me know 😄
  • More importantly this transform could be unsafe if the other operand of the + operation has overridden __add__ to do something else. What's the ruff policy around potentially unsafe operations? (I think some of the suggestions other ported rules give could be semantically different from the original code, but I'm not sure.)
  • I'm not a very established Rustacean, so there's no doubt my code isn't quite idiomatic. (For instance, is there a neater way to write that four-way match statement?)

Thanks for ruff, by the way! :)

@charliermarsh
Copy link
Member

Thank you for this contribution! It looks great. I'll look to add comments soon.

The main thing I'm trying to decide is whether this should be in SIM, or RUF. I'm not sure yet. Historically, we've avoided adding checks to the "ported" plugins even if they make sense to include conceptually, instead opting to put novel rules in RUF. The thinking is that the use of SIM is mostly about compatibility -- it lets users that are already using flake8-simplify adopt Ruff and enable SIM without any additional work. (The other argument would be that enabling SIM is a sign that you want simplification checks, which I may not be explaining well, but is subtly different.) So in that light, it'd be surprising if you enabled SIM and saw new checks that didn't exist "upstream".

In the future (not certain when), we'll likely recategorize the rules under more meaningful categories (like complexity -- see the initial proposal in #1774), while retaining this compatibility layer (so the flake8-simplify checks would continue to exist under flake8-simplify in some form, but also as part of a broader complexity category). At that point, the above issue wouldn't matter much, since anyone that enables the complexity category would get this rule regardless.

My gut is to keep this in RUF, or perhaps even introduce a new category along the lines of "experimental" or "nursery" (#1774) for now.

What do you think? Would love to hear your reactions.

@akx
Copy link
Contributor Author

akx commented Jan 18, 2023

Hi @charliermarsh, thanks for the kind words!

I'm absolutely more than fine moving this to RUF, or even some sort of RUX (for eXperimental or, um, possiblyXunsafe) category; I felt a bit bad shoving this under flake8-simplify's namespace to begin with anyway.

Beyond the categorization effort in #1774, maybe it might be worth it to be able to mark checks (like this) separately as unsafe or aggressive because you'll have to think a bit before blindly applying the suggestion – e.g. you'd have to opt-in to --fix-aggressive?

As an aside, I fixed the cargo fmt issue that appeared in the checks (and added it as a pre-commit check in #1968...)

@andersk
Copy link
Contributor

andersk commented Jan 18, 2023

Beyond the categorization effort in #1774, maybe it might be worth it to be able to mark checks (like this) separately as unsafe or aggressive because you'll have to think a bit before blindly applying the suggestion – e.g. you'd have to opt-in to --fix-aggressive?

Some prior art, if only for the amusement value: __CARGO_FIX_YOLO=1

@charliermarsh
Copy link
Member

Beyond the categorization effort in #1774, maybe it might be worth it to be able to mark checks (like this) separately as unsafe or aggressive because you'll have to think a bit before blindly applying the suggestion – e.g. you'd have to opt-in to --fix-aggressive?

One way we could model this is that we could omit this from the fixable list by default. (We already support turning autofix on and off on a per-rule basis: https://github.com/charliermarsh/ruff#unfixable.) Right now, fixable, by default, includes every rule. But we could instead start to omit some rules from fixable, and thus require users to opt-in explicitly. I'm not sure how we'd message this to users though.

@bluetech
Copy link
Contributor

bluetech commented Jan 18, 2023

From @andersk's link I saw that rustc/clippy assigns "applicability" to suggestions: https://doc.rust-lang.org/stable/nightly-rustc/rustc_errors/enum.Applicability.html

@charliermarsh
Copy link
Member

@akx - Let's go ahead and move this under RUF for now.

@bluetech - Applicability looks nice and is something we should support (defined at the Rule level, configurable at the command-line and in pyproject.toml).

\cc @not-my-profile who's probably interested in that too.

@akx akx force-pushed the list-plus-to-splat branch 4 times, most recently from f0be282 to 1a8d9ec Compare January 19, 2023 09:28
@akx
Copy link
Contributor Author

akx commented Jan 19, 2023

@charliermarsh Moved to RUF005. I also added a commented-out "failing test" in the fixture – chained concatenations aren't handled gracefully at all at present, but I suppose that could be improved later :)

@not-my-profile
Copy link
Contributor

I agree about rule applicability being something we should have ... I had already noticed it when looking at the clippy lint documentation ... I just opened #1997 for tracking that :)

@charliermarsh
Copy link
Member

Great, I'll look to get this merged today!

@akx akx changed the title Add new potentially unsafe "unpack instead of concatenating" check Add RUF005 "unpack instead of concatenating" check Jan 19, 2023
src/violations.rs Outdated Show resolved Hide resolved
src/checkers/tokens.rs Outdated Show resolved Hide resolved
resources/test/fixtures/ruff/RUF005.py Outdated Show resolved Hide resolved
@akx
Copy link
Contributor Author

akx commented Jan 19, 2023

@charliermarsh Thanks for the review!

I think this looks pretty good now:

 class Fun:
     words = ("how", "fun!")

     def yay(self):
         return self.words

 yay = Fun().yay
 
 foo = [4, 5, 6]
-bar = [1, 2, 3] + foo
+bar = [1, 2, 3, *foo]
 zoob = tuple(bar)
-quux = (7, 8, 9) + zoob
-spam = quux + (10, 11, 12)
+quux = (7, 8, 9, *zoob)
+spam = (*quux, 10, 11, 12)
 spom = list(spam)
-eggs = spom + [13, 14, 15]
-elatement = ("we all say", ) + yay()
-excitement = ("we all think", ) + Fun().yay()
-astonishment = ("we all feel", ) + Fun.words
+eggs = [*spom, 13, 14, 15]
+elatement = ("we all say", *yay())
+excitement = ("we all think", *Fun().yay())
+astonishment = ("we all feel", *Fun.words)
 
-chain = ['a', 'b', 'c'] + eggs + list(('yes', 'no', 'pants') + zoob)
+chain = ["a", "b", "c", *eggs, *list(("yes", "no", "pants", *zoob))]

@rhkleijn
Copy link

rhkleijn commented Jan 19, 2023

An interesting edge case to test would be empty tuples. Does it handle the following correctly?

baz = () + zoob

Unpacking as (*zoob) would be invalid here, I guess. It would need a trailing comma (*zoob,).

@akx
Copy link
Contributor Author

akx commented Jan 19, 2023

@rhkleijn I'm a bit surprised myself, but it does the right thing!

-baz = () + zoob
+baz = (*zoob,)

* Guard against complex splattees
* Wrap tuples in parens
@charliermarsh
Copy link
Member

@akx - How would you feel if I subsequently turned off autofix (by default) on this rule for now, instead requiring it to be opt-in (via the fixable setting in pyproject.toml)? I'm not completely decided either way, but I wanted to run it by you before I made that change.

@charliermarsh charliermarsh merged commit de54ff1 into astral-sh:main Jan 19, 2023
@charliermarsh
Copy link
Member

(Merging regardless but may follow-up to tweak the default fix list -- LMK what you think!)

@akx
Copy link
Contributor Author

akx commented Jan 19, 2023

@charliermarsh Sounds good to me! Thanks for the merge :)

renovate bot referenced this pull request in ixm-one/pytest-cmake-presets Jan 20, 2023
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [ruff](https://togithub.com/charliermarsh/ruff) | `^0.0.226` ->
`^0.0.227` |
[![age](https://badges.renovateapi.com/packages/pypi/ruff/0.0.227/age-slim)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://badges.renovateapi.com/packages/pypi/ruff/0.0.227/adoption-slim)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://badges.renovateapi.com/packages/pypi/ruff/0.0.227/compatibility-slim/0.0.226)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://badges.renovateapi.com/packages/pypi/ruff/0.0.227/confidence-slim/0.0.226)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>charliermarsh/ruff</summary>

###
[`v0.0.227`](https://togithub.com/charliermarsh/ruff/releases/tag/v0.0.227)

[Compare
Source](https://togithub.com/charliermarsh/ruff/compare/v0.0.226...v0.0.227)

#### What's Changed

- Drop `RuleCode` in favor of `Rule` enum with human-friendly names by
[@&#8203;not-my-profile](https://togithub.com/not-my-profile) in
[https://github.com/charliermarsh/ruff/pull/1941](https://togithub.com/charliermarsh/ruff/pull/1941)
- Make define_rule_mapping! set rule code as doc comment of variants by
[@&#8203;not-my-profile](https://togithub.com/not-my-profile) in
[https://github.com/charliermarsh/ruff/pull/1991](https://togithub.com/charliermarsh/ruff/pull/1991)
- Added pylint formatter by
[@&#8203;damienallen](https://togithub.com/damienallen) in
[https://github.com/charliermarsh/ruff/pull/1995](https://togithub.com/charliermarsh/ruff/pull/1995)
- Preserve unmatched comparators in `SIM109` by
[@&#8203;charliermarsh](https://togithub.com/charliermarsh) in
[https://github.com/charliermarsh/ruff/pull/1998](https://togithub.com/charliermarsh/ruff/pull/1998)
- Drop `Violation::placeholder` by
[@&#8203;not-my-profile](https://togithub.com/not-my-profile) in
[https://github.com/charliermarsh/ruff/pull/1996](https://togithub.com/charliermarsh/ruff/pull/1996)
- Apply #\[derive(Default)] fixes suggested by Clippy by
[@&#8203;akx](https://togithub.com/akx) in
[https://github.com/charliermarsh/ruff/pull/2000](https://togithub.com/charliermarsh/ruff/pull/2000)
- Avoid `SIM201` and `SIM202` errors in `__ne__` et al by
[@&#8203;charliermarsh](https://togithub.com/charliermarsh) in
[https://github.com/charliermarsh/ruff/pull/2001](https://togithub.com/charliermarsh/ruff/pull/2001)
- Fix that --explain panics by
[@&#8203;not-my-profile](https://togithub.com/not-my-profile) in
[https://github.com/charliermarsh/ruff/pull/2002](https://togithub.com/charliermarsh/ruff/pull/2002)
- Split up pydocstyle rules by [@&#8203;akx](https://togithub.com/akx)
in
[https://github.com/charliermarsh/ruff/pull/2003](https://togithub.com/charliermarsh/ruff/pull/2003)
- Add RUF005 "unpack instead of concatenating" check by
[@&#8203;akx](https://togithub.com/akx) in
[https://github.com/charliermarsh/ruff/pull/1957](https://togithub.com/charliermarsh/ruff/pull/1957)
- Enable autofix for `FitsOnOneLine` (`D200`) by
[@&#8203;charliermarsh](https://togithub.com/charliermarsh) in
[https://github.com/charliermarsh/ruff/pull/2006](https://togithub.com/charliermarsh/ruff/pull/2006)
- Change AsRef<str> impl for Rule to kebab-case by
[@&#8203;not-my-profile](https://togithub.com/not-my-profile) in
[https://github.com/charliermarsh/ruff/pull/2009](https://togithub.com/charliermarsh/ruff/pull/2009)
- Upgrade RustPython by
[@&#8203;charliermarsh](https://togithub.com/charliermarsh) in
[https://github.com/charliermarsh/ruff/pull/2011](https://togithub.com/charliermarsh/ruff/pull/2011)
- Avoid SIM401 in `elif` blocks by
[@&#8203;charliermarsh](https://togithub.com/charliermarsh) in
[https://github.com/charliermarsh/ruff/pull/2012](https://togithub.com/charliermarsh/ruff/pull/2012)
- Improve --explain output by
[@&#8203;not-my-profile](https://togithub.com/not-my-profile) in
[https://github.com/charliermarsh/ruff/pull/2010](https://togithub.com/charliermarsh/ruff/pull/2010)
- Avoid checking row types for single-name
[@&#8203;parametrize](https://togithub.com/parametrize) decorators by
[@&#8203;charliermarsh](https://togithub.com/charliermarsh) in
[https://github.com/charliermarsh/ruff/pull/2013](https://togithub.com/charliermarsh/ruff/pull/2013)

#### New Contributors

- [@&#8203;damienallen](https://togithub.com/damienallen) made their
first contribution in
[https://github.com/charliermarsh/ruff/pull/1995](https://togithub.com/charliermarsh/ruff/pull/1995)

**Full Changelog**:
astral-sh/ruff@v0.0.226...v0.0.227

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://app.renovatebot.com/dashboard#github/ixm-one/pytest-cmake-presets).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNC4xMDUuNCIsInVwZGF0ZWRJblZlciI6IjM0LjEwNS40In0=-->

Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants