Skip to content

Add Redundant else lint #6330

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

Merged
merged 2 commits into from
Dec 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2023,6 +2023,7 @@ Released 2018-09-13
[`redundant_closure`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
[`redundant_closure_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_call
[`redundant_closure_for_method_calls`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls
[`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else
[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern
[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
Expand Down
13 changes: 5 additions & 8 deletions clippy_lints/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,13 +405,10 @@ impl<'tcx> Functions {
break;
}
if in_comment {
match line.find("*/") {
Some(i) => {
line = &line[i + 2..];
in_comment = false;
continue;
},
None => break,
if let Some(i) = line.find("*/") {
line = &line[i + 2..];
in_comment = false;
continue;
}
} else {
let multi_idx = line.find("/*").unwrap_or_else(|| line.len());
Expand All @@ -423,8 +420,8 @@ impl<'tcx> Functions {
in_comment = true;
continue;
}
break;
}
break;
}
if code_in_line {
line_count += 1;
Expand Down
3 changes: 1 addition & 2 deletions clippy_lints/src/len_zero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,8 @@ fn check_impl_items(cx: &LateContext<'_>, item: &Item<'_>, impl_items: &[ImplIte
let is_empty = if let Some(is_empty) = impl_items.iter().find(|i| is_named_self(cx, i, "is_empty")) {
if cx.access_levels.is_exported(is_empty.id.hir_id) {
return;
} else {
"a private"
}
"a private"
} else {
"no corresponding"
};
Expand Down
4 changes: 4 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ mod question_mark;
mod ranges;
mod redundant_clone;
mod redundant_closure_call;
mod redundant_else;
mod redundant_field_names;
mod redundant_pub_crate;
mod redundant_static_lifetimes;
Expand Down Expand Up @@ -810,6 +811,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&ranges::REVERSED_EMPTY_RANGES,
&redundant_clone::REDUNDANT_CLONE,
&redundant_closure_call::REDUNDANT_CLOSURE_CALL,
&redundant_else::REDUNDANT_ELSE,
&redundant_field_names::REDUNDANT_FIELD_NAMES,
&redundant_pub_crate::REDUNDANT_PUB_CRATE,
&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES,
Expand Down Expand Up @@ -1113,6 +1115,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_early_pass(|| box items_after_statements::ItemsAfterStatements);
store.register_early_pass(|| box precedence::Precedence);
store.register_early_pass(|| box needless_continue::NeedlessContinue);
store.register_early_pass(|| box redundant_else::RedundantElse);
store.register_late_pass(|| box create_dir::CreateDir);
store.register_early_pass(|| box needless_arbitrary_self_type::NeedlessArbitrarySelfType);
store.register_early_pass(|| box redundant_static_lifetimes::RedundantStaticLifetimes);
Expand Down Expand Up @@ -1294,6 +1297,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF),
LintId::of(&ranges::RANGE_MINUS_ONE),
LintId::of(&ranges::RANGE_PLUS_ONE),
LintId::of(&redundant_else::REDUNDANT_ELSE),
LintId::of(&ref_option_ref::REF_OPTION_REF),
LintId::of(&shadow::SHADOW_UNRELATED),
LintId::of(&strings::STRING_ADD_ASSIGN),
Expand Down
5 changes: 2 additions & 3 deletions clippy_lints/src/matches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,10 +696,9 @@ fn check_single_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], exp
if stmts.len() == 1 && block_expr.is_none() || stmts.is_empty() && block_expr.is_some() {
// single statement/expr "else" block, don't lint
return;
} else {
// block with 2+ statements or 1 expr and 1+ statement
Some(els)
}
// block with 2+ statements or 1 expr and 1+ statement
Some(els)
} else {
// not a block, don't lint
return;
Expand Down
5 changes: 2 additions & 3 deletions clippy_lints/src/methods/unnecessary_filter_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,9 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc
}
}
return (true, false);
} else {
// We don't know. It might do anything.
return (true, true);
}
// We don't know. It might do anything.
return (true, true);
}
}
(true, true)
Expand Down
7 changes: 3 additions & 4 deletions clippy_lints/src/non_expressive_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,11 +409,10 @@ fn levenstein_not_1(a_name: &str, b_name: &str) -> bool {
if let Some(b2) = b_chars.next() {
// check if there's just one character inserted
return a != b2 || a_chars.ne(b_chars);
} else {
// tuple
// ntuple
return true;
}
// tuple
// ntuple
return true;
}
// for item in items
true
Expand Down
135 changes: 135 additions & 0 deletions clippy_lints/src/redundant_else.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use crate::utils::span_lint_and_help;
use rustc_ast::ast::{Block, Expr, ExprKind, Stmt, StmtKind};
use rustc_ast::visit::{walk_expr, Visitor};
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};

declare_clippy_lint! {
/// **What it does:** Checks for `else` blocks that can be removed without changing semantics.
///
/// **Why is this bad?** The `else` block adds unnecessary indentation and verbosity.
///
/// **Known problems:** Some may prefer to keep the `else` block for clarity.
///
/// **Example:**
///
/// ```rust
/// fn my_func(count: u32) {
/// if count == 0 {
/// print!("Nothing to do");
/// return;
/// } else {
/// print!("Moving on...");
/// }
/// }
/// ```
/// Use instead:
/// ```rust
/// fn my_func(count: u32) {
/// if count == 0 {
/// print!("Nothing to do");
/// return;
/// }
/// print!("Moving on...");
/// }
/// ```
pub REDUNDANT_ELSE,
pedantic,
"`else` branch that can be removed without changing semantics"
}

declare_lint_pass!(RedundantElse => [REDUNDANT_ELSE]);

impl EarlyLintPass for RedundantElse {
fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &Stmt) {
if in_external_macro(cx.sess, stmt.span) {
return;
}
// Only look at expressions that are a whole statement
let expr: &Expr = match &stmt.kind {
Copy link
Member

Choose a reason for hiding this comment

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

The type annotations do not seem to be needed (here and below)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The type annotation coerces &P<Expr> to &Expr. I think this is preferable to doing &**expr inside the match?

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, just removing the annotations compiles fine. I guess it could be related with match ergonomics being able to auto-deref if the pattern matches?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I think so.

  1. type annotations - auto-deref at binding - clear and smart-pointer-agnostic
  2. &**expr - manual deref before binding - less clear and smart-pointer-dependant
  3. neither - auto-deref at each usage - simplest code but inefficient

Copy link
Member

@ebroto ebroto Dec 8, 2020

Choose a reason for hiding this comment

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

I think it won't make a big difference, so I think it's fine to merge it as is. Thanks!


To explain my reasoning:

  1. I believe type annotations are generally less idiomatic if they are not needed to disambiguate (that's why I raised a concern).
  2. Correct me if I'm wrong, but I would say in this case using the annotation would just change the place where the dereference happens. From the let binding to the match/function call later, but I would say the number of derefs would be the same?

Copy link
Member

Choose a reason for hiding this comment

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

But yeah, I see your point, there would be cases where you could need more derefs than if you do it once when binding 👍

StmtKind::Expr(expr) | StmtKind::Semi(expr) => expr,
_ => return,
};
// if else
let (mut then, mut els): (&Block, &Expr) = match &expr.kind {
ExprKind::If(_, then, Some(els)) => (then, els),
_ => return,
};
loop {
if !BreakVisitor::default().check_block(then) {
// then block does not always break
return;
}
match &els.kind {
// else if else
ExprKind::If(_, next_then, Some(next_els)) => {
then = next_then;
els = next_els;
continue;
},
// else if without else
ExprKind::If(..) => return,
// done
_ => break,
}
}
span_lint_and_help(
cx,
REDUNDANT_ELSE,
els.span,
"redundant else block",
None,
"remove the `else` block and move the contents out",
);
}
}

/// Call `check` functions to check if an expression always breaks control flow
#[derive(Default)]
struct BreakVisitor {
is_break: bool,
}

impl<'ast> Visitor<'ast> for BreakVisitor {
fn visit_block(&mut self, block: &'ast Block) {
self.is_break = match block.stmts.as_slice() {
[.., last] => self.check_stmt(last),
_ => false,
};
}

fn visit_expr(&mut self, expr: &'ast Expr) {
self.is_break = match expr.kind {
ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) => true,
ExprKind::Match(_, ref arms) => arms.iter().all(|arm| self.check_expr(&arm.body)),
ExprKind::If(_, ref then, Some(ref els)) => self.check_block(then) && self.check_expr(els),
ExprKind::If(_, _, None)
// ignore loops for simplicity
| ExprKind::While(..) | ExprKind::ForLoop(..) | ExprKind::Loop(..) => false,
_ => {
walk_expr(self, expr);
return;
},
};
}
}

impl BreakVisitor {
fn check<T>(&mut self, item: T, visit: fn(&mut Self, T)) -> bool {
visit(self, item);
std::mem::replace(&mut self.is_break, false)
}

fn check_block(&mut self, block: &Block) -> bool {
self.check(block, Self::visit_block)
}

fn check_expr(&mut self, expr: &Expr) -> bool {
self.check(expr, Self::visit_expr)
}

fn check_stmt(&mut self, stmt: &Stmt) -> bool {
self.check(stmt, Self::visit_stmt)
}
}
Loading