-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Don't lint manual_let_else in cases where ? would work #10924
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
Conversation
r? @Manishearth (rustbot has picked a reviewer for you, use r? to override) |
6b657ce
to
37ad84d
Compare
additional r? @Centri3 |
Failed to set assignee to
|
Sure, will take a look in a bit! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me, great additions to the tests. Can you move pat_and_expr_can_be_question_mark
to clippy_utils
? Thanks!
clippy_lints/src/manual_let_else.rs
Outdated
/// We output `Some(a)` in the first instance, and `Some(FooBar { a, b })` in the second, because | ||
/// the question mark operator is applicable here. Callers have to check whether we are in a | ||
/// constant or not. | ||
pub fn pat_and_expr_can_be_question_mark<'a, 'hir>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should go in clippy_utils
imo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels a little bit specific to question mark
/manual_let_else
to me. Should I still move it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, the reason why I put it into a separate function and why it's shared by question_mark and manual_let_else is to make sure that there is definitely no overlap between the two. I don't think it's something that's supposed to be generally usable/useful, compared to say a function that checks if a pattern is refutable or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it stands currently it's definitely only useful for those two lints, but this sort of stuff should go in clippy_utils
regardless. In this case, you give a Pat
and an Expr
and get whether it can be represented as a ?
; that, imo, is general enough for it. Even if it's never really used elsewhere it could potentially save someone the effort of reimplementing it, then getting told it was already implemented in another lint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, I've moved it.
clippy_lints/src/manual_let_else.rs
Outdated
else_body: &Expr<'_>, | ||
) -> Option<&'a Pat<'hir>> { | ||
if let PatKind::TupleStruct(pat_path, [inner_pat], _) = pat.kind && | ||
is_res_lang_ctor(cx, cx.qpath_res(&pat_path, pat.hir_id), OptionSome) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: probably could also handle Result here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Result has the issue that ?
does Err
conversion while the else
block has no access to the err at all. So it is required to come up with something else, say either some manual bail!()
invocation, or constructing the Err(Something)
manually. That's why I think it has to be restricted to options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm thinking of it, one could lint for cases like:
let res = something_that_returns_result();
let Ok(val) = res else { return res };
or
let res = something_that_returns_result();
let Ok(val) = res else { return Err(res.unwrap_err()) };
It's I think way more rare to encounter that though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah that makes sense
clippy_lints/src/manual_let_else.rs
Outdated
/// We output `Some(a)` in the first instance, and `Some(FooBar { a, b })` in the second, because | ||
/// the question mark operator is applicable here. Callers have to check whether we are in a | ||
/// constant or not. | ||
pub fn pat_and_expr_can_be_question_mark<'a, 'hir>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The linting logic for let ... else
-> ?
is now called twice for 2 different lints. Usually we handle this by running those "conflicting" lints in the same pass and only check for the superseded lint, if the other lint does not trigger. You can see a lot of examples of that in the methods
module.
Do you think merging those two files into on pass and then applying the same approach would work for those lints?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The question_mark
lint is implemented right now as a visitor on expressions, while the manual_let_else
lint looks for statements. It makes a lot of sense for both to do it this way, because let v = if let Some(v) = ex { v } else { return None };
is a statement while for question_mark
it looks for stuff like if let Some(v) = ex { v } else { return None }
, which is an expression, and it doesn't matter if that expression is put into a local or is the start of a method chain, directly a function parameter, etc.
So while it's possible to merge the two lints into one pass, likely that would just similarly call the same function two times, just once through a check on statements, once through a check on expressions.
The only way I could think of unifying is that one changes the manual_let_else
lint to look for expressions, and then make it obtain the parent node, and if the parent node is a let
statement, it would continue from there.
Do you think this is preferable to having two passes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would do this way simpler:
fn check_let_some_else_return_none(..) -> bool
should return a bool
whether the lint was triggered.
in the check_stmt
part of the unified pass, you would then write the code like this:
if !check_let_some_else_return_none(...) {
check_manual_let_else(...);
}
And in check_manual_let_else
you put the code that currently is in the LateLintPass
impl of check_stmt
of the manual_let_else
lint.
Maybe it makes sense to include those lints in the matches
module. But I'm not 100% convinced if this is the best location for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The check_let_some_else_return_none
function does only check for let...else
, while the check_manual_let_else
function checks for let foo = if let () ...
patterns (and the match equivalent). Therefore, there is no overlap here. The overlap with the question mark lint would happen with the <QuestionMark as LateLintPass>::check_expr
function, which takes an expression instead of a statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have pushed a commit to put the two things into one pass. I feel it's better without this merging, but maybe you like it more than I do.
☔ The latest upstream changes (presumably #11001) made this pull request unmergeable. Please resolve the merge conflicts. |
…would work Also, lint question_mark for `let...else` clauses that can be simplified to use `?`. This lint isn't perfect as it doesn't support the unstable try blocks.
37ad84d
to
c6be621
Compare
e37727a
to
20dfaba
Compare
@bors delegate=centri3 (please approve this PR when ready) |
✌️ @Centri3, you can now approve this pull request! If @Manishearth told you to " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy with this version, just one minor nit
7086172
to
4dd78d3
Compare
If question_mark is allowed, there is no overlap any more, so we can just not suppress it.
4dd78d3
to
d80581c
Compare
I think this is done, I took another look and couldn't see anything. thanks! @bors r=Centri3,flip1995,Manishearth |
☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test |
@Centri3 @Manishearth thanks for the review! |
Don't lint
manual_let_else
where the question mark operator?
would be sufficient, that is, mostly in cases like:Also, this PR emits the
question_mark
lint forlet...else
patterns that could be written with?
(also, onlyreturn None
like cases).Fixes #8755