-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #6859 - magurotuna:if_then_some_else_none, r=giraffate
Implement new lint: if_then_some_else_none Resolves #6760 changelog: Added a new lint: `if_then_some_else_none`
- Loading branch information
Showing
5 changed files
with
285 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
use crate::utils; | ||
use if_chain::if_chain; | ||
use rustc_hir::{Expr, ExprKind}; | ||
use rustc_lint::{LateContext, LateLintPass, LintContext}; | ||
use rustc_middle::lint::in_external_macro; | ||
use rustc_semver::RustcVersion; | ||
use rustc_session::{declare_tool_lint, impl_lint_pass}; | ||
|
||
const IF_THEN_SOME_ELSE_NONE_MSRV: RustcVersion = RustcVersion::new(1, 50, 0); | ||
|
||
declare_clippy_lint! { | ||
/// **What it does:** Checks for if-else that could be written to `bool::then`. | ||
/// | ||
/// **Why is this bad?** Looks a little redundant. Using `bool::then` helps it have less lines of code. | ||
/// | ||
/// **Known problems:** None. | ||
/// | ||
/// **Example:** | ||
/// | ||
/// ```rust | ||
/// # let v = vec![0]; | ||
/// let a = if v.is_empty() { | ||
/// println!("true!"); | ||
/// Some(42) | ||
/// } else { | ||
/// None | ||
/// }; | ||
/// ``` | ||
/// | ||
/// Could be written: | ||
/// | ||
/// ```rust | ||
/// # let v = vec![0]; | ||
/// let a = v.is_empty().then(|| { | ||
/// println!("true!"); | ||
/// 42 | ||
/// }); | ||
/// ``` | ||
pub IF_THEN_SOME_ELSE_NONE, | ||
restriction, | ||
"Finds if-else that could be written using `bool::then`" | ||
} | ||
|
||
pub struct IfThenSomeElseNone { | ||
msrv: Option<RustcVersion>, | ||
} | ||
|
||
impl IfThenSomeElseNone { | ||
#[must_use] | ||
pub fn new(msrv: Option<RustcVersion>) -> Self { | ||
Self { msrv } | ||
} | ||
} | ||
|
||
impl_lint_pass!(IfThenSomeElseNone => [IF_THEN_SOME_ELSE_NONE]); | ||
|
||
impl LateLintPass<'_> for IfThenSomeElseNone { | ||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'_>) { | ||
if !utils::meets_msrv(self.msrv.as_ref(), &IF_THEN_SOME_ELSE_NONE_MSRV) { | ||
return; | ||
} | ||
|
||
if in_external_macro(cx.sess(), expr.span) { | ||
return; | ||
} | ||
|
||
// We only care about the top-most `if` in the chain | ||
if utils::parent_node_is_if_expr(expr, cx) { | ||
return; | ||
} | ||
|
||
if_chain! { | ||
if let ExprKind::If(ref cond, ref then, Some(ref els)) = expr.kind; | ||
if let ExprKind::Block(ref then_block, _) = then.kind; | ||
if let Some(ref then_expr) = then_block.expr; | ||
if let ExprKind::Call(ref then_call, [then_arg]) = then_expr.kind; | ||
if let ExprKind::Path(ref then_call_qpath) = then_call.kind; | ||
if utils::match_qpath(then_call_qpath, &utils::paths::OPTION_SOME); | ||
if let ExprKind::Block(ref els_block, _) = els.kind; | ||
if els_block.stmts.is_empty(); | ||
if let Some(ref els_expr) = els_block.expr; | ||
if let ExprKind::Path(ref els_call_qpath) = els_expr.kind; | ||
if utils::match_qpath(els_call_qpath, &utils::paths::OPTION_NONE); | ||
then { | ||
let cond_snip = utils::snippet_with_macro_callsite(cx, cond.span, "[condition]"); | ||
let cond_snip = if matches!(cond.kind, ExprKind::Unary(_, _) | ExprKind::Binary(_, _, _)) { | ||
format!("({})", cond_snip) | ||
} else { | ||
cond_snip.into_owned() | ||
}; | ||
let arg_snip = utils::snippet_with_macro_callsite(cx, then_arg.span, ""); | ||
let closure_body = if then_block.stmts.is_empty() { | ||
arg_snip.into_owned() | ||
} else { | ||
format!("{{ /* snippet */ {} }}", arg_snip) | ||
}; | ||
let help = format!( | ||
"consider using `bool::then` like: `{}.then(|| {})`", | ||
cond_snip, | ||
closure_body, | ||
); | ||
utils::span_lint_and_help( | ||
cx, | ||
IF_THEN_SOME_ELSE_NONE, | ||
expr.span, | ||
"this could be simplified with `bool::then`", | ||
None, | ||
&help, | ||
); | ||
} | ||
} | ||
} | ||
|
||
extract_msrv_attr!(LateContext); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
#![warn(clippy::if_then_some_else_none)] | ||
#![feature(custom_inner_attributes)] | ||
|
||
fn main() { | ||
// Should issue an error. | ||
let _ = if foo() { | ||
println!("true!"); | ||
Some("foo") | ||
} else { | ||
None | ||
}; | ||
|
||
// Should issue an error when macros are used. | ||
let _ = if matches!(true, true) { | ||
println!("true!"); | ||
Some(matches!(true, false)) | ||
} else { | ||
None | ||
}; | ||
|
||
// Should issue an error. Binary expression `o < 32` should be parenthesized. | ||
let x = Some(5); | ||
let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); | ||
|
||
// Should issue an error. Unary expression `!x` should be parenthesized. | ||
let x = true; | ||
let _ = if !x { Some(0) } else { None }; | ||
|
||
// Should not issue an error since the `else` block has a statement besides `None`. | ||
let _ = if foo() { | ||
println!("true!"); | ||
Some("foo") | ||
} else { | ||
eprintln!("false..."); | ||
None | ||
}; | ||
|
||
// Should not issue an error since there are more than 2 blocks in the if-else chain. | ||
let _ = if foo() { | ||
println!("foo true!"); | ||
Some("foo") | ||
} else if bar() { | ||
println!("bar true!"); | ||
Some("bar") | ||
} else { | ||
None | ||
}; | ||
|
||
let _ = if foo() { | ||
println!("foo true!"); | ||
Some("foo") | ||
} else { | ||
bar().then(|| { | ||
println!("bar true!"); | ||
"bar" | ||
}) | ||
}; | ||
|
||
// Should not issue an error since the `then` block has `None`, not `Some`. | ||
let _ = if foo() { None } else { Some("foo is false") }; | ||
|
||
// Should not issue an error since the `else` block doesn't use `None` directly. | ||
let _ = if foo() { Some("foo is true") } else { into_none() }; | ||
|
||
// Should not issue an error since the `then` block doesn't use `Some` directly. | ||
let _ = if foo() { into_some("foo") } else { None }; | ||
} | ||
|
||
fn _msrv_1_49() { | ||
#![clippy::msrv = "1.49"] | ||
// `bool::then` was stabilized in 1.50. Do not lint this | ||
let _ = if foo() { | ||
println!("true!"); | ||
Some(149) | ||
} else { | ||
None | ||
}; | ||
} | ||
|
||
fn _msrv_1_50() { | ||
#![clippy::msrv = "1.50"] | ||
let _ = if foo() { | ||
println!("true!"); | ||
Some(150) | ||
} else { | ||
None | ||
}; | ||
} | ||
|
||
fn foo() -> bool { | ||
unimplemented!() | ||
} | ||
|
||
fn bar() -> bool { | ||
unimplemented!() | ||
} | ||
|
||
fn into_some<T>(v: T) -> Option<T> { | ||
Some(v) | ||
} | ||
|
||
fn into_none<T>() -> Option<T> { | ||
None | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
error: this could be simplified with `bool::then` | ||
--> $DIR/if_then_some_else_none.rs:6:13 | ||
| | ||
LL | let _ = if foo() { | ||
| _____________^ | ||
LL | | println!("true!"); | ||
LL | | Some("foo") | ||
LL | | } else { | ||
LL | | None | ||
LL | | }; | ||
| |_____^ | ||
| | ||
= note: `-D clippy::if-then-some-else-none` implied by `-D warnings` | ||
= help: consider using `bool::then` like: `foo().then(|| { /* snippet */ "foo" })` | ||
|
||
error: this could be simplified with `bool::then` | ||
--> $DIR/if_then_some_else_none.rs:14:13 | ||
| | ||
LL | let _ = if matches!(true, true) { | ||
| _____________^ | ||
LL | | println!("true!"); | ||
LL | | Some(matches!(true, false)) | ||
LL | | } else { | ||
LL | | None | ||
LL | | }; | ||
| |_____^ | ||
| | ||
= help: consider using `bool::then` like: `matches!(true, true).then(|| { /* snippet */ matches!(true, false) })` | ||
|
||
error: this could be simplified with `bool::then` | ||
--> $DIR/if_then_some_else_none.rs:23:28 | ||
| | ||
LL | let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| | ||
= help: consider using `bool::then` like: `(o < 32).then(|| o)` | ||
|
||
error: this could be simplified with `bool::then` | ||
--> $DIR/if_then_some_else_none.rs:27:13 | ||
| | ||
LL | let _ = if !x { Some(0) } else { None }; | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| | ||
= help: consider using `bool::then` like: `(!x).then(|| 0)` | ||
|
||
error: this could be simplified with `bool::then` | ||
--> $DIR/if_then_some_else_none.rs:82:13 | ||
| | ||
LL | let _ = if foo() { | ||
| _____________^ | ||
LL | | println!("true!"); | ||
LL | | Some(150) | ||
LL | | } else { | ||
LL | | None | ||
LL | | }; | ||
| |_____^ | ||
| | ||
= help: consider using `bool::then` like: `foo().then(|| { /* snippet */ 150 })` | ||
|
||
error: aborting due to 5 previous errors | ||
|