Skip to content
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

fix: Fix false positives for "unnecessary else" diagnostic #16590

Merged
merged 8 commits into from
Feb 19, 2024
37 changes: 34 additions & 3 deletions crates/hir-ty/src/diagnostics/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use hir_expand::name;
use itertools::Itertools;
use rustc_hash::FxHashSet;
use rustc_pattern_analysis::usefulness::{compute_match_usefulness, ValidityConstraint};
use syntax::{ast, AstNode};
use tracing::debug;
use triomphe::Arc;
use typed_arena::Arena;
Expand Down Expand Up @@ -108,7 +109,7 @@ impl ExprValidator {
self.check_for_trailing_return(*body_expr, &body);
}
Expr::If { .. } => {
self.check_for_unnecessary_else(id, expr, &body);
self.check_for_unnecessary_else(id, expr, db);
}
Expr::Block { .. } => {
self.validate_block(db, expr);
Expand Down Expand Up @@ -336,19 +337,49 @@ impl ExprValidator {
}
}

fn check_for_unnecessary_else(&mut self, id: ExprId, expr: &Expr, body: &Body) {
fn check_for_unnecessary_else(&mut self, id: ExprId, expr: &Expr, db: &dyn HirDatabase) {
if let Expr::If { condition: _, then_branch, else_branch } = expr {
if else_branch.is_none() {
return;
}
if let Expr::Block { statements, tail, .. } = &body.exprs[*then_branch] {
if let Expr::Block { statements, tail, .. } = &self.body.exprs[*then_branch] {
let last_then_expr = tail.or_else(|| match statements.last()? {
Statement::Expr { expr, .. } => Some(*expr),
_ => None,
});
if let Some(last_then_expr) = last_then_expr {
let last_then_expr_ty = &self.infer[last_then_expr];
if last_then_expr_ty.is_never() {
// Only look at sources if the then branch diverges and we have an else branch.
let (_, source_map) = db.body_with_source_map(self.owner);
let Ok(source_ptr) = source_map.expr_syntax(id) else {
return;
};
let root = source_ptr.file_syntax(db.upcast());
let ast::Expr::IfExpr(if_expr) = source_ptr.value.to_node(&root) else {
return;
};
let mut top_if_expr = if_expr;
loop {
let parent = top_if_expr.syntax().parent();
let has_parent_expr_stmt_or_stmt_list =
parent.as_ref().map_or(false, |node| {
ast::ExprStmt::can_cast(node.kind())
| ast::StmtList::can_cast(node.kind())
davidsemakula marked this conversation as resolved.
Show resolved Hide resolved
});
if has_parent_expr_stmt_or_stmt_list {
// Only emit diagnostic if parent or direct ancestor is either
// an expr stmt or a stmt list.
break;
}
let Some(parent_if_expr) = parent.and_then(ast::IfExpr::cast) else {
// Bail if parent is neither an if expr, an expr stmt nor a stmt list.
return;
};
// Check parent if expr.
top_if_expr = parent_if_expr;
}

self.diagnostics
.push(BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr: id })
}
Expand Down
122 changes: 117 additions & 5 deletions crates/ide-diagnostics/src/handlers/remove_unnecessary_else.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use hir::{db::ExpandDatabase, diagnostics::RemoveUnnecessaryElse, HirFileIdExt};
use ide_db::{assists::Assist, source_change::SourceChange};
use itertools::Itertools;
use syntax::{
ast::{self, edit::IndentLevel},
ast::{
self,
edit::{AstNodeEdit, IndentLevel},
},
AstNode, SyntaxToken, TextRange,
};
use text_edit::TextEdit;
Expand Down Expand Up @@ -41,10 +44,15 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &RemoveUnnecessaryElse) -> Option<Vec<
indent = indent + 1;
}
let else_replacement = match if_expr.else_branch()? {
ast::ElseBranch::Block(ref block) => {
block.statements().map(|stmt| format!("\n{indent}{stmt}")).join("")
}
ast::ElseBranch::IfExpr(ref nested_if_expr) => {
ast::ElseBranch::Block(block) => block
.statements()
.map(|stmt| format!("\n{indent}{stmt}"))
.chain(block.tail_expr().map(|tail| format!("\n{indent}{tail}")))
.join(""),
ast::ElseBranch::IfExpr(mut nested_if_expr) => {
if has_parent_if_expr {
nested_if_expr = nested_if_expr.indent(IndentLevel(1))
}
format!("\n{indent}{nested_if_expr}")
}
};
Expand Down Expand Up @@ -171,6 +179,41 @@ fn test() {
);
}

#[test]
fn remove_unnecessary_else_for_return3() {
check_diagnostics_with_needless_return_disabled(
r#"
fn test(a: bool) -> i32 {
if a {
return 1;
} else {
//^^^^ 💡 weak: remove unnecessary else block
0
}
}
"#,
);
check_fix(
r#"
fn test(a: bool) -> i32 {
if a {
return 1;
} else$0 {
0
}
}
"#,
r#"
fn test(a: bool) -> i32 {
if a {
return 1;
}
0
}
"#,
);
}

#[test]
fn remove_unnecessary_else_for_return_in_child_if_expr() {
check_diagnostics_with_needless_return_disabled(
Expand Down Expand Up @@ -214,6 +257,41 @@ fn test() {
);
}

#[test]
fn remove_unnecessary_else_for_return_in_child_if_expr2() {
check_fix(
r#"
fn test() {
if foo {
do_something();
} else if qux {
return bar;
} else$0 if quux {
do_something_else();
} else {
do_something_else2();
}
}
"#,
r#"
fn test() {
if foo {
do_something();
} else {
if qux {
return bar;
}
if quux {
do_something_else();
} else {
do_something_else2();
}
}
}
"#,
);
}

#[test]
fn remove_unnecessary_else_for_break() {
check_diagnostics(
Expand Down Expand Up @@ -384,6 +462,40 @@ fn test() {
return bar;
}
}
"#,
);
}

#[test]
fn no_diagnostic_if_not_expr_stmt() {
check_diagnostics_with_needless_return_disabled(
r#"
fn test1() {
let _x = if a {
return;
} else {
1
};
}

fn test2() {
let _x = if a {
return;
} else if b {
return;
} else if c {
1
} else {
return;
};
}
"#,
);
check_diagnostics(
r#"
fn test3() -> u8 {
foo(if a { return 1 } else { 0 })
}
"#,
);
}
Expand Down
Loading