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

unnecessary_lazy_evaluations warns about str::len #8109

Open
jplatte opened this issue Dec 9, 2021 · 9 comments
Open

unnecessary_lazy_evaluations warns about str::len #8109

jplatte opened this issue Dec 9, 2021 · 9 comments
Labels
C-bug Category: Clippy is not doing the correct thing I-false-positive Issue: The lint was triggered on code it shouldn't have

Comments

@jplatte
Copy link
Contributor

jplatte commented Dec 9, 2021

Summary

str::len within an unwrap_or_else seems to cause unnecessary_lazy_evaluations to trigger even though it's a method call, not a constant which I assumed to be the only case where unnecessary_lazy_evaluations triggers.

Reproducer

I tried this code (Playground):

fn main() {
    let x = "str:with_separator";
    let first_part_pos = x.find(':').unwrap_or_else(|| x.len());
    dbg!(first_part_pos);
}

I expected to see this happen: No warnings

Instead, this happened:

warning: unnecessary closure used to substitute value for `Option::None`
 --> src/main.rs:3:26
  |
3 |     let first_part_pos = x.find(':').unwrap_or_else(|| x.len());
  |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `x.find(':').unwrap_or(x.len())`
  |
  = note: `#[warn(clippy::unnecessary_lazy_evaluations)]` on by default
  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations

Version

rustc 1.59.0-nightly (e6b883c74 2021-12-08)
binary: rustc
commit-hash: e6b883c74f49f32cb5d1cbad3457f2b8805a4a38
commit-date: 2021-12-08
host: x86_64-unknown-linux-gnu
release: 1.59.0-nightly
LLVM version: 13.0.0

Additional Labels

No response

@jplatte jplatte added the C-bug Category: Clippy is not doing the correct thing label Dec 9, 2021
@jplatte jplatte changed the title unnecessary_lazy_evaluations warns about foo.len() unnecessary_lazy_evaluations warns about str::len Dec 9, 2021
@xFrednet xFrednet added the I-false-positive Issue: The lint was triggered on code it shouldn't have label Dec 9, 2021
@Jarcho
Copy link
Contributor

Jarcho commented Dec 9, 2021

This is working as intended. str::len is effectively the same as reading a field, which has no reason to be lazily evaluated here.

@jplatte
Copy link
Contributor Author

jplatte commented Dec 9, 2021

Is str::len special-cased? Which other method calls are deemed "effectively the same as reading a field"?

@Qwaz
Copy link
Contributor

Qwaz commented Dec 12, 2021

The logic is determined by expr_eagerness() function. Method names with len(), is_empty(), and as_*() are recognized.

@hellow554
Copy link
Contributor

The same is true for Vec.len:

pub fn fo(x: Vec<usize>) -> usize {
    x.get(3).cloned().unwrap_or_else(|| x.len())
}

This is somewhat confusing, because function calls used to be wrapped in a closure.

I think at least (!) the error message should be extended to reflect the new behavoir or else people will get really confused (including me).

@camsteffen
Copy link
Contributor

The logic is determined by expr_eagerness() function. Method names with len(), is_empty(), and as_*() are recognized.

Yeah considering that this is a warn-by-default lint, I would consider changing that case to NoChange (allow the coder's preference). I personally like making function calls lazy as a simple rule.

@mlodato517
Copy link

mlodato517 commented Feb 24, 2022

Is this eagerness check also present for "simple" match statements like in this? I imagine so because it handles function calls in the match arms but just making sure!

Edit: Oh maybe the block for match is here which doesn't return self.eagerness = Lazy so maybe that makes sense? Super guessing just from the names of things 😅

@hellow554
Copy link
Contributor

What about determining if that function is constand only linting them?

@TethysSvensson
Copy link
Contributor

I love the idea of having this heuristic, even if it does make it a bit harder to write clippy-compliant code "by hand".

However I also agree that the introduction of the heuristic was handled somewhat sub-optimal and has at least introduced quite a bit of unnecessary churn in our code-base. This was from a combination of the confusing error message and the problem outlined in this comment on the relevant pull request:

cmyr added a commit to linebender/piet that referenced this issue Mar 28, 2022
These are slightly weird, but basically clippy now uses a heuristic to
identify certain method calls that it expects to be resolve to a field
access, and flags that these do not need to be wrapped in closures:

rust-lang/rust-clippy#8109 (comment)
cmyr added a commit to linebender/piet that referenced this issue Mar 28, 2022
These are slightly weird, but basically clippy now uses a heuristic to
identify certain method calls that it expects to be resolve to a field
access, and flags that these do not need to be wrapped in closures:

rust-lang/rust-clippy#8109 (comment)
cmyr added a commit to linebender/piet that referenced this issue Mar 28, 2022
These are slightly weird, but basically clippy now uses a heuristic to
identify certain method calls that it expects to be resolve to a field
access, and flags that these do not need to be wrapped in closures:

rust-lang/rust-clippy#8109 (comment)
cmyr added a commit to linebender/piet that referenced this issue Mar 28, 2022
These are slightly weird, but basically clippy now uses a heuristic to
identify certain method calls that it expects to be resolve to a field
access, and flags that these do not need to be wrapped in closures:

rust-lang/rust-clippy#8109 (comment)
@lopopolo
Copy link

lopopolo commented Apr 6, 2022

If we are to de-closure/de-combinator clippy's suggestion, I think the code would end up looking something like this:

fn main() {
    let x = "str:with_separator";
    let default = x.len();
    let first_part_pos = if let Some(first_part_pos) = x.find(':') {
        first_part_pos
    } else {
        default
    };
    dbg!(first_part_pos);
}

If I were writing the if let ... else version by hand, I'd probably make it look like this:

fn main() {
    let x = "str:with_separator";
    let first_part_pos = if let Some(first_part_pos) = x.find(':') {
        first_part_pos
    } else {
        x.len()
    };
    dbg!(first_part_pos);
}

I think the original sample with the closure given in this issue looks closer to how I'd write this same code with if let ... else and would love if this on-by-default lint would accept the code which has the closure.

david-perez added a commit to smithy-lang/smithy-rs that referenced this issue May 24, 2022
In Rust 1.60 we get Clippy lint warnings that make the build fail.

```
warning: unnecessary closure used to substitute value for `Option::None`
   --> inlineable/src/json_errors.rs:98:25
    |
98  |       if let Some(code) = error_type_from_header(headers)
    |  _________________________^
99  | |         .map_err(|_| DeserializeError::custom("X-Amzn-Errortype header was not valid UTF-8"))?
100 | |         .or_else(|| code.as_deref())
    | |____________________________________^
    |
    = note: `#[warn(clippy::unnecessary_lazy_evaluations)]` on by default
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
help: use `or` instead
    |
98  ~     if let Some(code) = error_type_from_header(headers)
99  +         .map_err(|_| DeserializeError::custom("X-Amzn-Errortype header was not valid UTF-8"))?.or(code.as_deref())
```

Since `len` is cheap and `deref` is supposed to be cheap, these resolve
to reading a pointer in assembly and they don't need to be lazily
evaluated.

https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
rust-lang/rust-clippy#8109
https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/eager_or_lazy.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: Clippy is not doing the correct thing I-false-positive Issue: The lint was triggered on code it shouldn't have
Projects
None yet
Development

No branches or pull requests

9 participants