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

single_match: Lint structs and tuple structs #8395

Closed
wants to merge 21 commits into from

Conversation

jubnzv
Copy link
Contributor

@jubnzv jubnzv commented Feb 5, 2022

These changes allow single_match lint to suggest the simplification of match constructions for structs and tuple structs with the same name, if they form an exhaustive match.

For example:

// lint: S(_, _) forms an exhaustive match, so it could be removed
match s {
    S(42, _a) => {},
    S(_, _) => {},
}

Context: #8322 (comment)


changelog: [single_match]: Lint tuple structs with the same name

These changes allow `single_match` lint to suggest the simplification of
`match` constructions for structs with the same name that forms an
exhaustive match.

For example:

```rust
// lint: S(_, _) forms an exhaustive match, so it could be removed
match s {
    S(42, _a) => {},
    S(_, _) => {},
}
```

See:
rust-lang#8322 (comment)
@rust-highfive
Copy link

r? @camsteffen

(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 Feb 5, 2022
@jubnzv jubnzv changed the title single_match: Lint structs with the same name single_match: Lint tuple structs with the same name Feb 5, 2022
Copy link
Contributor

@camsteffen camsteffen left a comment

Choose a reason for hiding this comment

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

I reviewed about 75% so far.

clippy_lints/src/matches.rs Outdated Show resolved Hide resolved
clippy_lints/src/matches.rs Outdated Show resolved Hide resolved
clippy_lints/src/matches.rs Outdated Show resolved Hide resolved
clippy_lints/src/matches.rs Outdated Show resolved Hide resolved
});
acc.push((path, ty));
true
(PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None), _) if contains_only_wilds(right) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

The BindingAnnotation shouldn't matter here.

Suggested change
(PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None), _) if contains_only_wilds(right) => {
(PatKind::Binding(.., ident, None), _) if contains_only_wilds(right) => {

Also, I think you can treat Binding the same as Wild?

Thirdly, I think we should handle the case with Some at the end. You could do this at the top of this function:

fn peel_subpatterns(pat) {
    while let PatKind::Binding(.., Some(sub)) = pat.kind {
        pat = sub;
    }
    pat
}
let left = peel_subpatterns(left);
let right = peel_subpatterns(right);

Copy link
Contributor Author

@jubnzv jubnzv Feb 8, 2022

Choose a reason for hiding this comment

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

I agree, I added support for bindings as you suggest.

But I have some problems with test suite after the changes.
The error states: '[ui] ui/patterns.rs' panicked at 'failed to apply suggestions for "tests/ui/patterns.rs" with rustfix: Cannot replace slice of data that was already replaced.

As far as I understand, the issue occurs when the test suite tries to apply rustfix on patterns.rs to check if it matches patterns.fixed. According to the stack trace, the problem is in rustfix library, and it is not related with clippy code. I found a few similar issues [1] [2] in clippy and rustfix repositories, but it didn't help me much.

So at the moment I'm not sure how to fix it and I need some time to figure it out. Any help would be helpful.

Copy link
Contributor

Choose a reason for hiding this comment

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

That error means that rustfix applied your suggestion, reran the lint on the updated code, and then got another suggestion for the same code. Like an infinite loop. Try manually applying the suggestions and then see if you get another suggestion in the same code.

Copy link
Contributor Author

@jubnzv jubnzv Feb 9, 2022

Choose a reason for hiding this comment

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

Thank you. I figured it out.

The problem was that after my changes clippy suggests to replace these lines:

    match v {
        Some(x) => (),
        y @ _ => (),
    }

To these:

if let Some(x) = x = { () }

But this code itself could be simplified to:

if let Some(x) = x = {}

So we got an infinite loop.

I fixed this file to keep its original logic and not trigger single_match lint.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm but I think the first example should trigger single_match. Which lint fired after the suggestion was applied?

Copy link
Contributor Author

@jubnzv jubnzv Feb 10, 2022

Choose a reason for hiding this comment

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

Hmm but I think the first example should trigger single_match

Yes, it seems logical. But for some reasons we have the following check for the els branch. And such cases are explicitly rejected.

According to git blame this check was added here. There are no tests or additional context there, so I'm not sure, what problem this solves. Probably, it is better to leave this logic as is in this MR and come back to it later?

Which lint fired after the suggestion was applied?

First, clippy suggests to replace y @ _ to y with redundant_pattern check. Then we get code that can be simplified with single_match check, and we try to execute rustfix again to fix it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, it seems logical. But for some reasons we have the following check for the els branch. And such cases are explicitly rejected.

That check is there because we only want to lint if the second arm is .. => () (or {}), so I that shouldn't be a problem for y @ _ => (),?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That check is there because we only want to lint if the second arm is .. => () (or {}), so I that shouldn't be a problem for y @ _ => (),?

Yes, and in that case we'll get the recursion error in rustfix.

clippy_lints/src/matches.rs Outdated Show resolved Hide resolved
@camelid
Copy link
Member

camelid commented Feb 6, 2022

I'm not sure what you mean by "with the same name". Any other name would result in a type error before clippy ran, right?

@jubnzv
Copy link
Contributor Author

jubnzv commented Feb 7, 2022

I'm not sure what you mean by "with the same name". Any other name would result in a type error before clippy ran, right?

Yes, that would be a compile error.

@bors
Copy link
Collaborator

bors commented Feb 7, 2022

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

@jubnzv jubnzv changed the title single_match: Lint tuple structs with the same name single_match: Lint structs and tuple structs Feb 8, 2022
clippy_lints/src/matches/single_match.rs Outdated Show resolved Hide resolved
}
true
},
fn contains_single_binding(pat: &Pat<'_>) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this function is needed since wilds and bindings are effectively the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to handle wildcards only when processing an els branch, because we cannot bind identifier to it.

For example, we don't want to lint the following case:

match s {
  a => (),
  a => (), // don't lint, we cannot bind `a` to the `else` branch
}

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can lint that case. The binding will be unused in the second branch.

Copy link
Contributor Author

@jubnzv jubnzv Feb 19, 2022

Choose a reason for hiding this comment

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

It doesn't work in such cases. We don't actually know, which branch will be if and which will be els, so after this change we'll get a lot of false positives.

I think, this out of scope of this PR, because it aims just to add support for Struct and TupleStruct patterns.

We could improve precision of this lint in different PR, right?

clippy_lints/src/matches/single_match.rs Show resolved Hide resolved
});
acc.push((path, ty));
true
(PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None), _) if contains_only_wilds(right) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, it seems logical. But for some reasons we have the following check for the els branch. And such cases are explicitly rejected.

That check is there because we only want to lint if the second arm is .. => () (or {}), so I that shouldn't be a problem for y @ _ => (),?

let left = peel_subpatterns(left);
let right = peel_subpatterns(right);
match (&left.kind, &right.kind) {
(PatKind::Wild, _) | (_, PatKind::Wild) => true,
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't this work?

Suggested change
(PatKind::Wild, _) | (_, PatKind::Wild) => true,
(PatKind::Wild | PatKind::Binding(..), _) | (_, PatKind::Wild | PatKind::Binding(..)) => true,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, because we should lint bindings iff the else branch (right pattern) contains wildcards only.

@bors
Copy link
Collaborator

bors commented Feb 24, 2022

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

@jubnzv
Copy link
Contributor Author

jubnzv commented Feb 27, 2022

@camsteffen friendly ping.
Could you please take a look at latest comments when you have time? I'm not sure we want to tweak any branch processing logic in the PR related to adding Struct and TupleStruct types.

@jubnzv
Copy link
Contributor Author

jubnzv commented Apr 6, 2022

Closed, since there is no response from maintainers.
Feel free to pick it, this PR is ready to merge.

@jubnzv jubnzv closed this Apr 6, 2022
@flip1995 flip1995 added S-inactive-closed Status: Closed due to inactivity and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Apr 6, 2022
@flip1995
Copy link
Member

flip1995 commented Apr 6, 2022

Sorry about not getting back to this. We're currently a bit overloaded with PRs and some maintainers (including me) are really busy with their $day_job. Thanks for working on this. I marked it as inactive-closed, so we don't lose track of it and can come back to it once we have a bit more capacity.

@jubnzv
Copy link
Contributor Author

jubnzv commented Apr 6, 2022

@flip1995 No problem, I understand. I don't have time for any side projects right now either, so I prefer to close it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-inactive-closed Status: Closed due to inactivity
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants