refactor(unnecessary_map_or): move .map_or(false, -> .is_some_and( transformation to manual_is_variant_and#16388
refactor(unnecessary_map_or): move .map_or(false, -> .is_some_and( transformation to manual_is_variant_and#16388ada4a wants to merge 1 commit intorust-lang:masterfrom
.map_or(false, -> .is_some_and( transformation to manual_is_variant_and#16388Conversation
|
Lintcheck changes for ad29684
This comment will be updated if you push new changes |
This comment has been minimized.
This comment has been minimized.
f4fa2a7 to
e9d33c6
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
e9d33c6 to
56a9bf9
Compare
This comment has been minimized.
This comment has been minimized.
|
Dependencies merged, so @rustbot ready |
| let _ = mac!(some 2).map_or(true, |x| x % 2 == 0); | ||
| let _ = mac!(some 2).map_or(false, |x| x % 2 == 0); |
There was a problem hiding this comment.
I know this is not part of this PR, mainly my own curiosity, is there any reason why these shouldn't lint?
There was a problem hiding this comment.
Well for the ones with mac! it's not that they should not lint, it's just that methods lints don't trigger on anything having macros.. as that can get a bit complicated
| if let ExprKind::Lit(def_kind) = def.kind | ||
| && let LitKind::Bool(def_bool) = def_kind.node | ||
| && let typeck = cx.typeck_results() | ||
| && let recv_ty = typeck.expr_ty_adjusted(recv) | ||
| && let wrap = match recv_ty.opt_diag_name(cx) { | ||
| Some(sym::Option) => "Some", | ||
| Some(sym::Result) => "Ok", | ||
| Some(_) | None => return false, | ||
| } | ||
| && typeck.expr_adjustments(recv).is_empty() | ||
| && let ExprKind::Closure(map_closure) = map.kind | ||
| && let closure_body = cx.tcx.hir_body(map_closure.body) | ||
| && let closure_body_value = closure_body.value.peel_blocks() | ||
| && let ExprKind::Binary(op, l, r) = closure_body_value.kind | ||
| && let [param] = closure_body.params | ||
| && let PatKind::Binding(_, hir_id, _, _) = param.pat.kind | ||
| // checking that map_or is one of the following: | ||
| // .map_or(false, |x| x == y) | ||
| // .map_or(false, |x| y == x) - swapped comparison | ||
| // .map_or(true, |x| x != y) | ||
| // .map_or(true, |x| y != x) - swapped comparison | ||
| && ((BinOpKind::Eq == op.node && !def_bool) || (BinOpKind::Ne == op.node && def_bool)) | ||
| && let non_binding_location = if l.res_local_id() == Some(hir_id) { r } else { l } | ||
| && switch_to_eager_eval(cx, non_binding_location) | ||
| // if it's both then that's a strange edge case and | ||
| // we can just ignore it, since by default clippy will error on this | ||
| && (l.res_local_id() == Some(hir_id)) != (r.res_local_id() == Some(hir_id)) | ||
| && !is_local_used(cx, non_binding_location, hir_id) | ||
| && let l_ty = typeck.expr_ty(l) | ||
| && l_ty == typeck.expr_ty(r) | ||
| && let Some(partial_eq) = cx.tcx.lang_items().eq_trait() | ||
| && implements_trait(cx, recv_ty, partial_eq, &[recv_ty.into()]) | ||
| && is_copy(cx, l_ty) | ||
| && !is_from_proc_macro(cx, expr) |
There was a problem hiding this comment.
You'd probably enjoy Scala's for comprehensions
There was a problem hiding this comment.
Ha:) But yeah I like let-chains a lot, they're easily one of my favorite newer features in Rust. Don't you just love it when the whole lint definition is just one big chain:)
| // Don't lint, `unnecessary_map_or` will take over | ||
| #[derive(PartialEq)] | ||
| struct S1; | ||
| let r: Result<i32, S1> = Ok(3); | ||
| let _ = r.map_or(false, |x| x == 7); | ||
| //~^ unnecessary_map_or |
There was a problem hiding this comment.
Note: If you allow unnecessary_map_or here, then manual_is_variant_and won't lint either.
| if !unnecessary_map_or::check(cx, expr, recv, def, map) { | ||
| manual_is_variant_and::check_map_or(cx, expr, recv, span, def, map, self.msrv); | ||
| } |
There was a problem hiding this comment.
I'm not a huge fan of this solution, but I also lack the expertise on Clippy to see a better solution.
I think my main issue is that it entangles two independent lints. My ideal solution in this case would be to produce both lints (and both suggestions), but I'm not sure if this is allowed by Clippy. And if it could take any kind of priority to choose which one to apply automatically.
As a note, a real issue with this approach is that disallowing the deciding lint (unnecessary_map_or) doesn't enable the secondary one. Check the note in the manual_is_variant_and testcase.
There was a problem hiding this comment.
In any case I'd add a comment explaining that these two lints overlap each other, and why priority is given to one over the other.
There was a problem hiding this comment.
I'm not a huge fan of this solution, but I also lack the expertise on Clippy to see a better solution.
I understand what you mean, and don't like this intertwining itself, but unfortunately I don't know of a better way myself (I'm relatively new here^^). I do need to note, though, that this approach is used here and there in the repository.
My ideal solution in this case would be to produce both lints (and both suggestions), but I'm not sure if this is allowed by Clippy
There isn't anything forbidding this, but if there are multiple possible conflicting suggestions on the same expression, we usually try to only show the most relevant one. In this case, transforming .map_or(false, |n| n == 5) to == Some(5) is a lot more helpful than just to .is_some_and(|n| n == 5).
a real issue with this approach is that disallowing the deciding [..] doesn't enable the secondary one
Right, that's a good catch! I think it could be solved by refining unnecessary_map_or::check to do something like:
if /* conditions */ {
let mut fired = false;
span_lint_and_then(
/* .. */,
|diag| {
/* .. */
fired = true;
}
);
fired
} else {
false
}Though I agree that this is not very pretty.
In any case I'd add a comment explaining that these two lints overlap each other, and why priority is given to one over the other.
That's a good idea. Added a comment
There was a problem hiding this comment.
I think it could be solved by refining unnecessary_map_or::check to do something like:
I agree it's not very elegant, it'd be interesting if span_lint_and_then returned a boolean itself, or even better the result of the closure wrapped in an Option<>.
But anyways, not a big deal and it seems to be in line with the rest of the codebase.
56a9bf9 to
96610fa
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
96610fa to
270dd8d
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
270dd8d to
c5bd8ef
Compare
This comment has been minimized.
This comment has been minimized.
c5bd8ef to
211df42
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
211df42 to
80b2123
Compare
This comment has been minimized.
This comment has been minimized.
|
r? clippy |
This comment has been minimized.
This comment has been minimized.
80b2123 to
6f715e1
Compare
This comment has been minimized.
This comment has been minimized.
6f715e1 to
c66f3da
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
c66f3da to
ad29684
Compare
|
This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
View all comments
This is the third commit of #16383
Resolves #15998
Diffs best viewed with whitespace ignored and
--color-movedchangelog: [
unnecessary_map_or]: move.map_or(false,->.is_some_and(and similar transformations tomanual_is_variant_and