Auto merge of #11135 - smoelius:unwrap_or_else_default-fp, r=Centri3

Fix `unwrap_or_else_default` false positive

This PR fixes a false positive in the handling of `unwrap_or_else` with a default value when the value is needed for type inference.

An easy example to exhibit the false positive is the following:
```rust
    let option = None;
    option.unwrap_or_else(Vec::new).push(1);
```
The following code would not compile, because the fact that the value is a `Vec` has been lost:
```rust
    let option = None;
    option.unwrap_or_default().push(1);
```
The fix is to:
- implement a heuristic to tell whether an expression's type can be determined purely from its subexpressions, and the arguments and locals they use;
- apply the heuristic to `unwrap_or_else`'s receiver.

The heuristic returns false when applied to `option` in the above example, but it returns true when applied to `option` in either of the following examples:
```rust
    let option: Option<Vec<u64>> = None;
    option.unwrap_or_else(Vec::new).push(1);
```
```rust
    let option = None::<Vec<u64>>;
    option.unwrap_or_else(Vec::new).push(1);
```

(Aside: https://github.com/rust-lang/rust-clippy/pull/10120 unfairly contained multiple changes in one PR. I am trying to break that PR up into smaller pieces.)

---

changelog: FP: [`unwrap_or_else_default`]: No longer lints if the default value is needed for type inference
This commit is contained in:
bors 2023-07-19 11:37:30 +00:00
commit 7a34143fa3
7 changed files with 568 additions and 2 deletions

View file

@ -74,4 +74,41 @@ fn unwrap_or_else_default() {
empty_string.unwrap_or_default();
}
fn type_certainty(option: Option<Vec<u64>>) {
option.unwrap_or_default().push(1);
let option: std::option::Option<std::vec::Vec<u64>> = None;
option.unwrap_or_default().push(1);
let option: Option<Vec<u64>> = None;
option.unwrap_or_default().push(1);
let option = std::option::Option::<std::vec::Vec<u64>>::None;
option.unwrap_or_default().push(1);
let option = Option::<Vec<u64>>::None;
option.unwrap_or_default().push(1);
let option = std::option::Option::None::<std::vec::Vec<u64>>;
option.unwrap_or_default().push(1);
let option = Option::None::<Vec<u64>>;
option.unwrap_or_default().push(1);
let option = None::<Vec<u64>>;
option.unwrap_or_default().push(1);
// should not be changed: type annotation with infer, unconcretized initializer
let option: Option<Vec<_>> = None;
option.unwrap_or_else(Vec::new).push(1);
// should not be changed: no type annotation, unconcretized initializer
let option = Option::None;
option.unwrap_or_else(Vec::new).push(1);
// should not be changed: no type annotation, unconcretized initializer
let option = None;
option.unwrap_or_else(Vec::new).push(1);
}
fn main() {}

View file

@ -74,4 +74,41 @@ fn unwrap_or_else_default() {
empty_string.unwrap_or_else(|| "".to_string());
}
fn type_certainty(option: Option<Vec<u64>>) {
option.unwrap_or_else(Vec::new).push(1);
let option: std::option::Option<std::vec::Vec<u64>> = None;
option.unwrap_or_else(Vec::new).push(1);
let option: Option<Vec<u64>> = None;
option.unwrap_or_else(Vec::new).push(1);
let option = std::option::Option::<std::vec::Vec<u64>>::None;
option.unwrap_or_else(Vec::new).push(1);
let option = Option::<Vec<u64>>::None;
option.unwrap_or_else(Vec::new).push(1);
let option = std::option::Option::None::<std::vec::Vec<u64>>;
option.unwrap_or_else(Vec::new).push(1);
let option = Option::None::<Vec<u64>>;
option.unwrap_or_else(Vec::new).push(1);
let option = None::<Vec<u64>>;
option.unwrap_or_else(Vec::new).push(1);
// should not be changed: type annotation with infer, unconcretized initializer
let option: Option<Vec<_>> = None;
option.unwrap_or_else(Vec::new).push(1);
// should not be changed: no type annotation, unconcretized initializer
let option = Option::None;
option.unwrap_or_else(Vec::new).push(1);
// should not be changed: no type annotation, unconcretized initializer
let option = None;
option.unwrap_or_else(Vec::new).push(1);
}
fn main() {}

View file

@ -36,5 +36,53 @@ error: use of `.unwrap_or_else(..)` to construct default value
LL | empty_string.unwrap_or_else(|| "".to_string());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `empty_string.unwrap_or_default()`
error: aborting due to 6 previous errors
error: use of `.unwrap_or_else(..)` to construct default value
--> $DIR/unwrap_or_else_default.rs:78:5
|
LL | option.unwrap_or_else(Vec::new).push(1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()`
error: use of `.unwrap_or_else(..)` to construct default value
--> $DIR/unwrap_or_else_default.rs:81:5
|
LL | option.unwrap_or_else(Vec::new).push(1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()`
error: use of `.unwrap_or_else(..)` to construct default value
--> $DIR/unwrap_or_else_default.rs:84:5
|
LL | option.unwrap_or_else(Vec::new).push(1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()`
error: use of `.unwrap_or_else(..)` to construct default value
--> $DIR/unwrap_or_else_default.rs:87:5
|
LL | option.unwrap_or_else(Vec::new).push(1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()`
error: use of `.unwrap_or_else(..)` to construct default value
--> $DIR/unwrap_or_else_default.rs:90:5
|
LL | option.unwrap_or_else(Vec::new).push(1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()`
error: use of `.unwrap_or_else(..)` to construct default value
--> $DIR/unwrap_or_else_default.rs:93:5
|
LL | option.unwrap_or_else(Vec::new).push(1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()`
error: use of `.unwrap_or_else(..)` to construct default value
--> $DIR/unwrap_or_else_default.rs:96:5
|
LL | option.unwrap_or_else(Vec::new).push(1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()`
error: use of `.unwrap_or_else(..)` to construct default value
--> $DIR/unwrap_or_else_default.rs:99:5
|
LL | option.unwrap_or_else(Vec::new).push(1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `option.unwrap_or_default()`
error: aborting due to 14 previous errors