summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/blocks_in_conditions.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/clippy/clippy_lints/src/blocks_in_conditions.rs')
-rw-r--r--src/tools/clippy/clippy_lints/src/blocks_in_conditions.rs137
1 files changed, 137 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/blocks_in_conditions.rs b/src/tools/clippy/clippy_lints/src/blocks_in_conditions.rs
new file mode 100644
index 000000000..1417e230a
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/blocks_in_conditions.rs
@@ -0,0 +1,137 @@
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::source::snippet_block_with_applicability;
+use clippy_utils::ty::implements_trait;
+use clippy_utils::visitors::{for_each_expr, Descend};
+use clippy_utils::{get_parent_expr, higher};
+use core::ops::ControlFlow;
+use rustc_errors::Applicability;
+use rustc_hir::{BlockCheckMode, Expr, ExprKind, MatchSource};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::declare_lint_pass;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `if` conditions that use blocks containing an
+ /// expression, statements or conditions that use closures with blocks.
+ ///
+ /// ### Why is this bad?
+ /// Style, using blocks in the condition makes it hard to read.
+ ///
+ /// ### Examples
+ /// ```no_run
+ /// # fn somefunc() -> bool { true };
+ /// if { true } { /* ... */ }
+ ///
+ /// if { let x = somefunc(); x } { /* ... */ }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```no_run
+ /// # fn somefunc() -> bool { true };
+ /// if true { /* ... */ }
+ ///
+ /// let res = { let x = somefunc(); x };
+ /// if res { /* ... */ }
+ /// ```
+ #[clippy::version = "1.45.0"]
+ pub BLOCKS_IN_CONDITIONS,
+ style,
+ "useless or complex blocks that can be eliminated in conditions"
+}
+
+declare_lint_pass!(BlocksInConditions => [BLOCKS_IN_CONDITIONS]);
+
+const BRACED_EXPR_MESSAGE: &str = "omit braces around single expression condition";
+
+impl<'tcx> LateLintPass<'tcx> for BlocksInConditions {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ let Some((cond, keyword, desc)) = higher::If::hir(expr)
+ .map(|hif| (hif.cond, "if", "an `if` condition"))
+ .or(if let ExprKind::Match(match_ex, _, MatchSource::Normal) = expr.kind {
+ Some((match_ex, "match", "a `match` scrutinee"))
+ } else {
+ None
+ })
+ else {
+ return;
+ };
+ let complex_block_message = &format!(
+ "in {desc}, avoid complex blocks or closures with blocks; \
+ instead, move the block or closure higher and bind it with a `let`",
+ );
+
+ if let ExprKind::Block(block, _) = &cond.kind {
+ if block.rules == BlockCheckMode::DefaultBlock {
+ if block.stmts.is_empty() {
+ if let Some(ex) = &block.expr {
+ // don't dig into the expression here, just suggest that they remove
+ // the block
+ if expr.span.from_expansion() || ex.span.from_expansion() {
+ return;
+ }
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ BLOCKS_IN_CONDITIONS,
+ cond.span,
+ BRACED_EXPR_MESSAGE,
+ "try",
+ snippet_block_with_applicability(cx, ex.span, "..", Some(expr.span), &mut applicability)
+ .to_string(),
+ applicability,
+ );
+ }
+ } else {
+ let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
+ if span.from_expansion() || expr.span.from_expansion() {
+ return;
+ }
+ // move block higher
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ BLOCKS_IN_CONDITIONS,
+ expr.span.with_hi(cond.span.hi()),
+ complex_block_message,
+ "try",
+ format!(
+ "let res = {}; {keyword} res",
+ snippet_block_with_applicability(cx, block.span, "..", Some(expr.span), &mut applicability),
+ ),
+ applicability,
+ );
+ }
+ }
+ } else {
+ let _: Option<!> = for_each_expr(cond, |e| {
+ if let ExprKind::Closure(closure) = e.kind {
+ // do not lint if the closure is called using an iterator (see #1141)
+ if let Some(parent) = get_parent_expr(cx, e)
+ && let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind
+ && let caller = cx.typeck_results().expr_ty(self_arg)
+ && let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator)
+ && implements_trait(cx, caller, iter_id, &[])
+ {
+ return ControlFlow::Continue(Descend::No);
+ }
+
+ let body = cx.tcx.hir().body(closure.body);
+ let ex = &body.value;
+ if let ExprKind::Block(block, _) = ex.kind {
+ if !body.value.span.from_expansion() && !block.stmts.is_empty() {
+ span_lint(cx, BLOCKS_IN_CONDITIONS, ex.span, complex_block_message);
+ return ControlFlow::Continue(Descend::No);
+ }
+ }
+ }
+ ControlFlow::Continue(Descend::Yes)
+ });
+ }
+ }
+}