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

Introduce needless_option_take lint #8665

Merged
merged 9 commits into from
Apr 17, 2022

Conversation

b-ncMN
Copy link
Contributor

@b-ncMN b-ncMN commented Apr 8, 2022

  • Followed [lint naming conventions][lint_naming]
  • Added passing UI tests (including committed .stderr file)
  • cargo test passes locally
  • Executed cargo dev update_lints
  • Added lint documentation
  • Run cargo dev fmt

Fixes #8618

changelog: Introduce [needless_option_take] lint

@rust-highfive
Copy link

r? @llogiq

(rust-highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Apr 8, 2022
@b-ncMN b-ncMN marked this pull request as draft April 8, 2022 18:29
@b-ncMN b-ncMN changed the title Draft: Introduce option_take_on_temporary lints Introduce option_take_on_temporary lints Apr 8, 2022
@llogiq
Copy link
Contributor

llogiq commented Apr 8, 2022

Hi there! Thank you for making clippy better. If you need anything, let me know.

@b-ncMN b-ncMN marked this pull request as ready for review April 9, 2022 16:37
@b-ncMN
Copy link
Contributor Author

b-ncMN commented Apr 9, 2022

I think this is ready

@b-ncMN
Copy link
Contributor Author

b-ncMN commented Apr 9, 2022

Is it normal for these to fail?
They don't seem to be related to my changes

@Alexendoo
Copy link
Member

@b-ncMN
Copy link
Contributor Author

b-ncMN commented Apr 9, 2022

The test output can be pretty large but you want to look out for the diffs in them, e.g.

https://github.com/rust-lang/rust-clippy/runs/5955895411?check_suite_focus=true#step:7:939 https://github.com/rust-lang/rust-clippy/runs/5955895411?check_suite_focus=true#step:7:1416

I see,
but those files weren't added by me, this is weird

@Alexendoo
Copy link
Member

Since the lint is enabled by default it's also run on all the existing test files. The diff there is between the expected stderr in the .stderr file and the result of running clippy on it

@b-ncMN
Copy link
Contributor Author

b-ncMN commented Apr 9, 2022

Since the lint is enabled by default it's also run on all the existing test files. The diff there is between the expected stderr in the .stderr file and the result of running clippy on it

Right!
So those failing lints need to be modified as well, is that correct?
For a second I thought I merged other people's branches into my branch but no, thank god :^)

what I don't understand too are the redundant-closures lint errors, that's not a lint of mine,
was that not modified when that lint was added?

I figured I probably need to edit the errors coming from my lint first, and then see what happens, this may be a side effect 🤔

Copy link
Member

@Alexendoo Alexendoo left a comment

Choose a reason for hiding this comment

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

The redundant closure errors are part of the existing tests, you can see them in https://github.com/rust-lang/rust-clippy/blob/c7e68638431b2a6639fcbeb44de790619de3b9b1/tests/ui/eta.stderr

The diff shown is between that file and the result of running clippy with your additions on https://github.com/rust-lang/rust-clippy/blob/c7e68638431b2a6639fcbeb44de790619de3b9b1/tests/ui/eta.rs. It shows a new error pointing at

Thunk(Box::new(move || option.take().unwrap()()))

This is a false positive though, rather than updating the tests the lint should be changed so it doesn't fire, we don't want to be linting things of the form option.take()

tests/ui/option_take_on_temporary.rs Outdated Show resolved Hide resolved
clippy_lints/src/methods/option_take_on_temporary.rs Outdated Show resolved Hide resolved
@b-ncMN b-ncMN force-pushed the option_take_on_temporary branch 3 times, most recently from e1830d0 to e9c5e50 Compare April 10, 2022 21:30
@xFrednet
Copy link
Member

xFrednet commented Apr 11, 2022

From the failed CI, the lint message should start with a lowercase

- Called `Option::take()` on a temporary value
+ called `Option::take()` on a temporary value

@b-ncMN
Copy link
Contributor Author

b-ncMN commented Apr 11, 2022

From the failed CI, the lint message should start with a lowercase

- Called `Option::take()` on a temporary value

+ called `Option::take()` on a temporary value

I know, just didn't have time to apply it yet.

@bors
Copy link
Collaborator

bors commented Apr 11, 2022

☔ The latest upstream changes (presumably #8624) made this pull request unmergeable. Please resolve the merge conflicts.

@b-ncMN
Copy link
Contributor Author

b-ncMN commented Apr 12, 2022

Can I get some reviews again ? Meanwhile I rebase the branch

@b-ncMN b-ncMN requested a review from llogiq April 12, 2022 17:38
@b-ncMN
Copy link
Contributor Author

b-ncMN commented Apr 12, 2022

Do we need an MSRV for rust features from rust 1.0.0 ? https://doc.rust-lang.org/src/core/option.rs.html#1550

@llogiq
Copy link
Contributor

llogiq commented Apr 13, 2022

Do we need an MSRV for rust features from rust 1.0.0 ? https://doc.rust-lang.org/src/core/option.rs.html#1550

I don't think so, we don't support pre-1.0.0 Rust versions.

This lint checks if Option::take() is used on a temporary value (a value
that is not of type &mut Option and that is not a Place expression) to
suggest omitting take()
@b-ncMN b-ncMN force-pushed the option_take_on_temporary branch 2 times, most recently from eb9a327 to fe4299f Compare April 14, 2022 10:58
The implemented checks are for checking if the expression is either of
type `Option` and isn't a syntactical place
This implements a machine applicable suggestion to any matched usage of
`.as_ref().take()``
This changes the lint from the suspicious category to the complexity category
This fixes the errors occuring while running the ui tests
Instead of type checking the entire expression (causing a false
positive), only type check for a subset of the expression (the receiver of
the matched function: `take()`)
This checks if the expression has one of `core`, `option`, `Option` or
`as_ref` in its path, this avoids false positives
This adds test to make sure correct behavior of lint
- The first test's option variable is not a temporary variable
- The second test does not make usage of `take()`
- The third test makes usage of `take()` and uses a temporary variable
@b-ncMN b-ncMN requested a review from llogiq April 14, 2022 11:28
@b-ncMN
Copy link
Contributor Author

b-ncMN commented Apr 14, 2022

So wait, my code now currently does not check for temporaries, just as_ref, that's not what I had in mind at first

@llogiq
Copy link
Contributor

llogiq commented Apr 16, 2022

That's ok. Checking for temporaries is a bit harder, as you'll have to check if the bindings are used later on (which clippy_utils has methods for, if memory serves). But I'd be OK with merging this less capable lint and noting in the docs that using a temporary binding will currently lead to a false negative, then tackling that in a followup PR.

@b-ncMN
Copy link
Contributor Author

b-ncMN commented Apr 16, 2022

That's ok. Checking for temporaries is a bit harder, as you'll have to check if the bindings are used later on (which clippy_utils has methods for, if memory serves). But I'd be OK with merging this less capable lint and noting in the docs that using a temporary binding will currently lead to a false negative, then tackling that in a followup PR.

Ok then let's do this, I will try to tackle the next step.
And I still wonder why using a temporary binding will produce a false positive 🤔

@b-ncMN b-ncMN changed the title Introduce option_take_on_temporary lints Introduce needless_option_take lint Apr 16, 2022
@llogiq
Copy link
Contributor

llogiq commented Apr 17, 2022

Thank you!

@bors r+

@bors
Copy link
Collaborator

bors commented Apr 17, 2022

📌 Commit 2903b56 has been approved by llogiq

@bors
Copy link
Collaborator

bors commented Apr 17, 2022

⌛ Testing commit 2903b56 with merge e5ebece...

@bors
Copy link
Collaborator

bors commented Apr 17, 2022

☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test
Approved by: llogiq
Pushing e5ebece to master...

@bors bors merged commit e5ebece into rust-lang:master Apr 17, 2022
@b-ncMN b-ncMN deleted the option_take_on_temporary branch April 17, 2022 21:24
@b-ncMN
Copy link
Contributor Author

b-ncMN commented Apr 19, 2022

@llogiq do you mind pointing me to where I need to look at to get started on the followup ? I briefly tried searching for functions related to temporaries in the docs but didn't find anything

@llogiq
Copy link
Contributor

llogiq commented Apr 19, 2022

Sorry for letting you wait so long, but I'm on vacation and have only spotty network. I'll try to get back to you within two days.

@llogiq
Copy link
Contributor

llogiq commented Apr 20, 2022

Ok, so currently we only catch _.as_ref().take(). We can extend that by creating a function that recurses over all method calls, that returns an enum OptionCallChain { FoundAsRef, FoundLocal(HirId), FoundNone } with FoundAsRef returned if we encounter a method call to Option::as_ref, and FoundLocal(_) if we have a ExprPath(_) referencing a local variable.

In the FoundLocal case, we must visit the parent body with the take call's HirId, ignoring everything before that call and checking if we encounter that local variable again. We also need to check if the local was given to the function by ref. Then we only lint if the local is not encountered in the body or arguments. You can find Visitor implementations in clippy_utils for reference.

@b-ncMN
Copy link
Contributor Author

b-ncMN commented Apr 22, 2022

In the FoundLocal case, we must visit the parent body with the take call's HirId, ignoring everything before that call and checking if we encounter that local variable again. We also need to check if the local was given to the function by ref. Then we only lint if the local is not encountered in the body or arguments. You can find Visitor implementations in clippy_utils for reference.

I'm confused about how to achieve the second part, the one just up there :
I've seen https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/intravisit/trait.Visitor.html#method.visit_id, but I don't seem to have any walk_id function.
my currently looks like

struct TakeVisitor<'a, 'tcx> {
    cx: &'a LateContext<'tcx>,
    identifiers: HashSet<HirId>,
}

impl<'a, 'tcx> Visitor<'tcx> for TakeVisitor<'a, 'tcx> {
    fn visit_id(&mut self, hir_id: HirId) {
        self.identifiers.insert(hir_id);
        // walk_id()
    }
}

fn recurse_over_methods(cx: &LateContext<'_>, expr: &Expr<'_>) -> OptionCallChain {
    //dbg!(expr);
    match expr.kind {
        rustc_hir::ExprKind::MethodCall(method_path, [value], _) => {
            match method_path.ident.as_str() {
                // Checks if last function in line == "take"
                "take" => match value.kind {
                    rustc_hir::ExprKind::MethodCall(value_method_path, [_], _) => {
                        dbg!(method_path, value);
                        if value_method_path.ident.as_str() == "as_ref" {
                            return OptionCallChain::FoundAsRef;
                        }
                    },
                    _ => {},
                },
                _ => {},
            }
        },
        _ => {
            println!("not a method call");
        },
    };
    OptionCallChain::FoundAsRef
}

I hope I've understanded the part correctly, in which, I need to use walk_id to walk over the IDs, and insert each one of those into a HashSet if it does not match any previous entry

@llogiq
Copy link
Contributor

llogiq commented Apr 23, 2022

There is nothing in an id we would have to walk.

We need to visit the parent item, not the method call. However, the visitor should only count uses of the local after encountering the take expr (which you can check by storing and comparing the expr.id).

So you need to a) walk the method chain until finding an ExprPath (which would be the foo in foo.take()), get its def I'd and the expr.id of the take() method call), b) get the parent item and if it is a fn check if foo comes from a function argument and if not c) run the visitor to check for later uses of our foo (because those would observe the change of our take).

@b-ncMN
Copy link
Contributor Author

b-ncMN commented Apr 23, 2022

There is nothing in an id we would have to walk.

We need to visit the parent item, not the method call. However, the visitor should only count uses of the local after encountering the take expr (which you can check by storing and comparing the expr.id).

So you need to a) walk the method chain until finding an ExprPath (which would be the foo in foo.take()), get its def I'd and the expr.id of the take() method call), b) get the parent item and if it is a fn check if foo comes from a function argument and if not c) run the visitor to check for later uses of our foo (because those would observe the change of our take).

Oh, right,
I was a little confused by "In the FoundLocal case, we must visit the parent body with the take call's HirId"
So I need to walk the expr until I find ExprPath and find out if it contains take ?
What is the parent item you refer to in B ?
Could we take contact in Zulip ? I think it would be easier to communicate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a lint for Option::take on a temporary
6 participants