diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index eaf1a14ec34..b1c9241670b 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -5,5 +5,5 @@ pub use hir_expand::diagnostics::{ }; pub use hir_ty::diagnostics::{ IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, - NoSuchField, + NoSuchField, RemoveThisSemicolon, }; diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs index b58fe0ed772..e59487e544b 100644 --- a/crates/hir_ty/src/diagnostics.rs +++ b/crates/hir_ty/src/diagnostics.rs @@ -216,6 +216,30 @@ impl Diagnostic for MissingOkInTailExpr { } } +#[derive(Debug)] +pub struct RemoveThisSemicolon { + pub file: HirFileId, + pub expr: AstPtr, +} + +impl Diagnostic for RemoveThisSemicolon { + fn code(&self) -> DiagnosticCode { + DiagnosticCode("remove-this-semicolon") + } + + fn message(&self) -> String { + "Remove this semicolon".to_string() + } + + fn display_source(&self) -> InFile { + InFile { file_id: self.file, value: self.expr.clone().into() } + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + // Diagnostic: break-outside-of-loop // // This diagnostic is triggered if `break` keyword is used outside of a loop. diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs index 434b19354f2..84941570630 100644 --- a/crates/hir_ty/src/diagnostics/expr.rs +++ b/crates/hir_ty/src/diagnostics/expr.rs @@ -2,7 +2,7 @@ use std::sync::Arc; -use hir_def::{path::path, resolver::HasResolver, AdtId, DefWithBodyId}; +use hir_def::{expr::Statement, path::path, resolver::HasResolver, AdtId, DefWithBodyId}; use hir_expand::diagnostics::DiagnosticSink; use rustc_hash::FxHashSet; use syntax::{ast, AstPtr}; @@ -12,6 +12,7 @@ use crate::{ diagnostics::{ match_check::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness}, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, MissingPatFields, + RemoveThisSemicolon, }, utils::variant_data, ApplicationTy, InferenceResult, Ty, TypeCtor, @@ -76,8 +77,12 @@ impl<'a, 'b> ExprValidator<'a, 'b> { } } let body_expr = &body[body.body_expr]; - if let Expr::Block { tail: Some(t), .. } = body_expr { - self.validate_results_in_tail_expr(body.body_expr, *t, db); + if let Expr::Block { statements, tail, .. } = body_expr { + if let Some(t) = tail { + self.validate_results_in_tail_expr(body.body_expr, *t, db); + } else if let Some(Statement::Expr(id)) = statements.last() { + self.validate_missing_tail_expr(body.body_expr, *id, db); + } } } @@ -317,6 +322,34 @@ impl<'a, 'b> ExprValidator<'a, 'b> { } } } + + fn validate_missing_tail_expr( + &mut self, + body_id: ExprId, + possible_tail_id: ExprId, + db: &dyn HirDatabase, + ) { + let mismatch = match self.infer.type_mismatch_for_expr(body_id) { + Some(m) => m, + None => return, + }; + + let possible_tail_ty = match self.infer.type_of_expr.get(possible_tail_id) { + Some(ty) => ty, + None => return, + }; + + if mismatch.actual != Ty::unit() || mismatch.expected != *possible_tail_ty { + return; + } + + let (_, source_map) = db.body_with_source_map(self.owner.into()); + + if let Ok(source_ptr) = source_map.expr_syntax(possible_tail_id) { + self.sink + .push(RemoveThisSemicolon { file: source_ptr.file_id, expr: source_ptr.value }); + } + } } pub fn record_literal_missing_fields( diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index d09f3a0a1f5..049f808dc60 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -131,6 +131,9 @@ pub(crate) fn diagnostics( .on::(|d| { res.borrow_mut().push(diagnostic_with_fix(d, &sema)); }) + .on::(|d| { + res.borrow_mut().push(diagnostic_with_fix(d, &sema)); + }) .on::(|d| { res.borrow_mut().push(warning_with_fix(d, &sema)); }) diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index a900d7bae45..13240672f0b 100644 --- a/crates/ide/src/diagnostics/fixes.rs +++ b/crates/ide/src/diagnostics/fixes.rs @@ -4,7 +4,7 @@ use hir::{ db::AstDatabase, diagnostics::{ Diagnostic, IncorrectCase, MissingFields, MissingOkInTailExpr, NoSuchField, - UnresolvedModule, + RemoveThisSemicolon, UnresolvedModule, }, HasSource, HirDisplay, InFile, Semantics, VariantDef, }; @@ -105,6 +105,27 @@ impl DiagnosticWithFix for MissingOkInTailExpr { } } +impl DiagnosticWithFix for RemoveThisSemicolon { + fn fix(&self, sema: &Semantics) -> Option { + let root = sema.db.parse_or_expand(self.file)?; + + let semicolon = self + .expr + .to_node(&root) + .syntax() + .parent() + .and_then(ast::ExprStmt::cast) + .and_then(|expr| expr.semicolon_token())? + .text_range(); + + let edit = TextEdit::delete(semicolon); + let source_change = + SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); + + Some(Fix::new("Remove this semicolon", source_change, semicolon)) + } +} + impl DiagnosticWithFix for IncorrectCase { fn fix(&self, sema: &Semantics) -> Option { let root = sema.db.parse_or_expand(self.file)?;