diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 3016c92a0c..0f1963e831 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -5,7 +5,7 @@ //! be expressed in terms of hir types themselves. use cfg::{CfgExpr, CfgOptions}; use either::Either; -use hir_def::path::ModPath; +use hir_def::{path::ModPath, type_ref::Mutability}; use hir_expand::{name::Name, HirFileId, InFile}; use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; @@ -28,6 +28,7 @@ macro_rules! diagnostics { } diagnostics![ + AddReferenceHere, BreakOutsideOfLoop, InactiveCode, IncorrectCase, @@ -154,4 +155,10 @@ pub struct MissingMatchArms { pub arms: AstPtr, } +#[derive(Debug)] +pub struct AddReferenceHere { + pub expr: InFile>, + pub mutability: Mutability, +} + pub use hir_ty::diagnostics::IncorrectCase; diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 9a77607d67..72d5dbc781 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -82,8 +82,8 @@ use crate::db::{DefDatabase, HirDatabase}; pub use crate::{ attrs::{HasAttrs, Namespace}, diagnostics::{ - AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, MacroError, - MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr, + AddReferenceHere, AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, + MacroError, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr, MissingUnsafe, NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro, @@ -1251,6 +1251,12 @@ impl Function { Err(SyntheticSyntax) => (), } } + BodyValidationDiagnostic::AddReferenceHere { arg_expr, mutability } => { + match source_map.expr_syntax(arg_expr) { + Ok(expr) => acc.push(AddReferenceHere { expr, mutability }.into()), + Err(SyntheticSyntax) => (), + } + } } } diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs index 9a6d5e082b..17de5dfe2a 100644 --- a/crates/hir_ty/src/diagnostics/expr.rs +++ b/crates/hir_ty/src/diagnostics/expr.rs @@ -5,7 +5,8 @@ use std::{cell::RefCell, sync::Arc}; use hir_def::{ - expr::Statement, path::path, resolver::HasResolver, AssocItemId, DefWithBodyId, HasModule, + expr::Statement, path::path, resolver::HasResolver, type_ref::Mutability, AssocItemId, + DefWithBodyId, HasModule, }; use hir_expand::name; use itertools::Either; @@ -17,7 +18,7 @@ use crate::{ self, usefulness::{compute_match_usefulness, expand_pattern, MatchCheckCtx, PatternArena}, }, - AdtId, InferenceResult, Interner, TyExt, TyKind, + AdtId, InferenceResult, Interner, Ty, TyExt, TyKind, }; pub(crate) use hir_def::{ @@ -50,6 +51,10 @@ pub enum BodyValidationDiagnostic { MissingMatchArms { match_expr: ExprId, }, + AddReferenceHere { + arg_expr: ExprId, + mutability: Mutability, + }, } impl BodyValidationDiagnostic { @@ -118,6 +123,22 @@ impl ExprValidator { self.validate_missing_tail_expr(body.body_expr, *id); } } + + let infer = &self.infer; + let diagnostics = &mut self.diagnostics; + + infer + .expr_type_mismatches() + .filter_map(|(expr, mismatch)| { + let (expr_without_ref, mutability) = + check_missing_refs(infer, expr, &mismatch.expected)?; + + Some((expr_without_ref, mutability)) + }) + .for_each(|(arg_expr, mutability)| { + diagnostics + .push(BodyValidationDiagnostic::AddReferenceHere { arg_expr, mutability }); + }); } fn check_for_filter_map_next(&mut self, db: &dyn HirDatabase) { @@ -491,3 +512,30 @@ fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResul walk(pat, body, infer, &mut has_type_mismatches); !has_type_mismatches } + +fn check_missing_refs( + infer: &InferenceResult, + arg: ExprId, + param: &Ty, +) -> Option<(ExprId, Mutability)> { + let arg_ty = infer.type_of_expr.get(arg)?; + + let reference_one = arg_ty.as_reference(); + let reference_two = param.as_reference(); + + match (reference_one, reference_two) { + (None, Some((referenced_ty, _, mutability))) if referenced_ty == arg_ty => { + Some((arg, Mutability::from_mutable(matches!(mutability, chalk_ir::Mutability::Mut)))) + } + (None, Some((referenced_ty, _, mutability))) => match referenced_ty.kind(&Interner) { + TyKind::Slice(subst) if matches!(arg_ty.kind(&Interner), TyKind::Array(arr_subst, _) if arr_subst == subst) => { + Some(( + arg, + Mutability::from_mutable(matches!(mutability, chalk_ir::Mutability::Mut)), + )) + } + _ => None, + }, + _ => None, + } +} diff --git a/crates/ide_diagnostics/src/handlers/add_reference_here.rs b/crates/ide_diagnostics/src/handlers/add_reference_here.rs new file mode 100644 index 0000000000..db24fd6cce --- /dev/null +++ b/crates/ide_diagnostics/src/handlers/add_reference_here.rs @@ -0,0 +1,163 @@ +use hir::db::AstDatabase; +use ide_db::source_change::SourceChange; +use syntax::AstNode; +use text_edit::TextEdit; + +use crate::{fix, Assist, Diagnostic, DiagnosticsContext}; + +// Diagnostic: add-reference-here +// +// This diagnostic is triggered when there's a missing referencing of expression. +pub(crate) fn add_reference_here( + ctx: &DiagnosticsContext<'_>, + d: &hir::AddReferenceHere, +) -> Diagnostic { + Diagnostic::new( + "add-reference-here", + "add reference here", + ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, + ) + .with_fixes(fixes(ctx, d)) +} + +fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::AddReferenceHere) -> Option> { + let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; + let arg_expr = d.expr.value.to_node(&root); + + let arg_with_ref = format!("&{}{}", d.mutability.as_keyword_for_ref(), arg_expr.syntax()); + + let arg_range = arg_expr.syntax().text_range(); + let edit = TextEdit::replace(arg_range, arg_with_ref); + let source_change = + SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + + Some(vec![fix("add_reference_here", "Add reference here", source_change, arg_range)]) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_diagnostics, check_fix}; + + #[test] + fn missing_reference() { + check_diagnostics( + r#" +fn main() { + test(123); + //^^^ 💡 error: add reference here +} +fn test(arg: &i32) {} +"#, + ); + } + + #[test] + fn test_add_reference_to_int() { + check_fix( + r#" +fn main() { + test(123$0); +} +fn test(arg: &i32) {} + "#, + r#" +fn main() { + test(&123); +} +fn test(arg: &i32) {} + "#, + ); + } + + #[test] + fn test_add_mutable_reference_to_int() { + check_fix( + r#" +fn main() { + test($0123); +} +fn test(arg: &mut i32) {} + "#, + r#" +fn main() { + test(&mut 123); +} +fn test(arg: &mut i32) {} + "#, + ); + } + + #[test] + fn test_add_reference_to_array() { + check_fix( + r#" +fn main() { + test($0[1, 2, 3]); +} +fn test(arg: &[i32]) {} + "#, + r#" +fn main() { + test(&[1, 2, 3]); +} +fn test(arg: &[i32]) {} + "#, + ); + } + + #[test] + fn test_add_reference_to_method_call() { + check_fix( + r#" +fn main() { + Test.call_by_ref($0123); +} +struct Test; +impl Test { + fn call_by_ref(&self, arg: &i32) {} +} + "#, + r#" +fn main() { + Test.call_by_ref(&123); +} +struct Test; +impl Test { + fn call_by_ref(&self, arg: &i32) {} +} + "#, + ); + } + + #[test] + fn test_add_reference_to_let_stmt() { + check_fix( + r#" +fn main() { + let test: &i32 = $0123; +} + "#, + r#" +fn main() { + let test: &i32 = &123; +} + "#, + ); + } + + #[test] + fn test_add_mutable_reference_to_let_stmt() { + check_fix( + r#" +fn main() { + let test: &mut i32 = $0123; +} + "#, + r#" +fn main() { + let test: &mut i32 = &mut 123; +} + "#, + ); + } +} diff --git a/crates/ide_diagnostics/src/lib.rs b/crates/ide_diagnostics/src/lib.rs index a3dc88b5f5..9cb34d5a1a 100644 --- a/crates/ide_diagnostics/src/lib.rs +++ b/crates/ide_diagnostics/src/lib.rs @@ -24,6 +24,7 @@ //! don't yet have a great pattern for how to do them properly. mod handlers { + pub(crate) mod add_reference_here; pub(crate) mod break_outside_of_loop; pub(crate) mod inactive_code; pub(crate) mod incorrect_case; @@ -176,6 +177,7 @@ pub fn diagnostics( for diag in diags { #[rustfmt::skip] let d = match diag { + AnyDiagnostic::AddReferenceHere(d) => handlers::add_reference_here::add_reference_here(&ctx, &d), AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d), AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d), AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d),