From d53146a01325ed7da4e59932b3948d4e517c7536 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 8 Nov 2025 21:15:45 +0800 Subject: [PATCH] Add ide-assist: add_explicit_method_call_deref Insert explicit method call reference and dereferences. Example --- ```rust struct Foo; impl Foo { fn foo(&self) {} } fn test() { Foo$0.$0foo(); } ``` -> ```rust struct Foo; impl Foo { fn foo(&self) {} } fn test() { (&Foo).foo(); } ``` --- .../src/handlers/add_explicit_dot_deref.rs | 214 ++++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/ide-assists/src/tests/generated.rs | 21 ++ crates/syntax/src/ast/make.rs | 7 + .../src/ast/syntax_factory/constructors.rs | 16 ++ 5 files changed, 260 insertions(+) create mode 100644 crates/ide-assists/src/handlers/add_explicit_dot_deref.rs diff --git a/crates/ide-assists/src/handlers/add_explicit_dot_deref.rs b/crates/ide-assists/src/handlers/add_explicit_dot_deref.rs new file mode 100644 index 000000000000..d27a6b4ce770 --- /dev/null +++ b/crates/ide-assists/src/handlers/add_explicit_dot_deref.rs @@ -0,0 +1,214 @@ +use hir::{Adjust, Mutability}; +use ide_db::assists::AssistId; +use itertools::Itertools; +use syntax::{ + AstNode, T, + ast::{self, syntax_factory::SyntaxFactory}, +}; + +use crate::{AssistContext, Assists}; + +// Assist: add_explicit_method_call_deref +// +// Insert explicit method call reference and dereferences. +// +// ``` +// struct Foo; +// impl Foo { fn foo(&self) {} } +// fn test() { +// Foo$0.$0foo(); +// } +// ``` +// -> +// ``` +// struct Foo; +// impl Foo { fn foo(&self) {} } +// fn test() { +// (&Foo).foo(); +// } +// ``` +pub(crate) fn add_explicit_method_call_deref( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + if ctx.has_empty_selection() { + return None; + } + let dot_token = ctx.find_token_syntax_at_offset(T![.])?; + if ctx.selection_trimmed() != dot_token.text_range() { + return None; + } + let method_call_expr = dot_token.parent().and_then(ast::MethodCallExpr::cast)?; + let receiver = method_call_expr.receiver()?; + + let adjustments = ctx.sema.expr_adjustments(&receiver)?; + let adjustments = + adjustments.into_iter().filter_map(|adjust| simple_adjust_kind(adjust.kind)).collect_vec(); + if adjustments.is_empty() { + return None; + } + + acc.add( + AssistId::refactor_rewrite("add_explicit_method_call_deref"), + "Insert explicit method call derefs", + dot_token.text_range(), + |builder| { + let mut edit = builder.make_editor(method_call_expr.syntax()); + let make = SyntaxFactory::without_mappings(); + let mut expr = receiver.clone(); + + for adjust_kind in adjustments { + expr = adjust_kind.wrap_expr(expr, &make); + } + + expr = make.expr_paren(expr).into(); + edit.replace(receiver.syntax(), expr.syntax()); + + builder.add_file_edits(ctx.vfs_file_id(), edit); + }, + ) +} + +fn simple_adjust_kind(adjust: Adjust) -> Option { + match adjust { + Adjust::NeverToAny | Adjust::Pointer(_) => None, + Adjust::Deref(_) => Some(AdjustKind::Deref), + Adjust::Borrow(hir::AutoBorrow::Ref(mutability)) => Some(AdjustKind::Ref(mutability)), + Adjust::Borrow(hir::AutoBorrow::RawPtr(mutability)) => Some(AdjustKind::RefRaw(mutability)), + } +} + +enum AdjustKind { + Deref, + Ref(Mutability), + RefRaw(Mutability), +} + +impl AdjustKind { + fn wrap_expr(self, expr: ast::Expr, make: &SyntaxFactory) -> ast::Expr { + match self { + AdjustKind::Deref => make.expr_prefix(T![*], expr).into(), + AdjustKind::Ref(mutability) => make.expr_ref(expr, mutability.is_mut()), + AdjustKind::RefRaw(mutability) => make.expr_raw_ref(expr, mutability.is_mut()), + } + } +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist; + + use super::*; + + #[test] + fn works_ref() { + check_assist( + add_explicit_method_call_deref, + r#" + struct Foo; + impl Foo { fn foo(&self) {} } + fn test() { + Foo$0.$0foo(); + }"#, + r#" + struct Foo; + impl Foo { fn foo(&self) {} } + fn test() { + (&Foo).foo(); + }"#, + ); + } + + #[test] + fn works_ref_mut() { + check_assist( + add_explicit_method_call_deref, + r#" + struct Foo; + impl Foo { fn foo(&mut self) {} } + fn test() { + Foo$0.$0foo(); + }"#, + r#" + struct Foo; + impl Foo { fn foo(&mut self) {} } + fn test() { + (&mut Foo).foo(); + }"#, + ); + } + + #[test] + fn works_deref() { + check_assist( + add_explicit_method_call_deref, + r#" + struct Foo; + impl Foo { fn foo(self) {} } + fn test() { + let foo = &Foo; + foo$0.$0foo(); + }"#, + r#" + struct Foo; + impl Foo { fn foo(self) {} } + fn test() { + let foo = &Foo; + (*foo).foo(); + }"#, + ); + } + + #[test] + fn works_reborrow() { + check_assist( + add_explicit_method_call_deref, + r#" + struct Foo; + impl Foo { fn foo(&self) {} } + fn test() { + let foo = &mut Foo; + foo$0.$0foo(); + }"#, + r#" + struct Foo; + impl Foo { fn foo(&self) {} } + fn test() { + let foo = &mut Foo; + (&*foo).foo(); + }"#, + ); + } + + #[test] + fn works_deref_reborrow() { + check_assist( + add_explicit_method_call_deref, + r#" + //- minicore: deref + struct Foo; + struct Bar; + impl core::ops::Deref for Foo { + type Target = Bar; + fn deref(&self) -> &Self::Target {} + } + impl Bar { fn bar(&self) {} } + fn test() { + let foo = &mut Foo; + foo$0.$0bar(); + }"#, + r#" + struct Foo; + struct Bar; + impl core::ops::Deref for Foo { + type Target = Bar; + fn deref(&self) -> &Self::Target {} + } + impl Bar { fn bar(&self) {} } + fn test() { + let foo = &mut Foo; + (&**foo).bar(); + }"#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 4b4aa9427955..df9fc4f740c9 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -105,6 +105,7 @@ mod handlers { pub(crate) type Handler = fn(&mut Assists, &AssistContext<'_>) -> Option<()>; mod add_braces; + mod add_explicit_dot_deref; mod add_explicit_enum_discriminant; mod add_explicit_type; mod add_label_to_loop; @@ -241,6 +242,7 @@ mod handlers { &[ // These are alphabetic for the foolish consistency add_braces::add_braces, + add_explicit_dot_deref::add_explicit_method_call_deref, add_explicit_enum_discriminant::add_explicit_enum_discriminant, add_explicit_type::add_explicit_type, add_label_to_loop::add_label_to_loop, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 7f0836abdf3c..17c74e20516d 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -69,6 +69,27 @@ enum TheEnum { ) } +#[test] +fn doctest_add_explicit_method_call_deref() { + check_doc_test( + "add_explicit_method_call_deref", + r#####" +struct Foo; +impl Foo { fn foo(&self) {} } +fn test() { + Foo$0.$0foo(); +} +"#####, + r#####" +struct Foo; +impl Foo { fn foo(&self) {} } +fn test() { + (&Foo).foo(); +} +"#####, + ) +} + #[test] fn doctest_add_explicit_type() { check_doc_test( diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index dba39204e32e..7fbf9eeba459 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -690,6 +690,13 @@ pub fn expr_macro(path: ast::Path, tt: ast::TokenTree) -> ast::MacroExpr { pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr { expr_from_text(&if exclusive { format!("&mut {expr}") } else { format!("&{expr}") }) } +pub fn expr_raw_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr { + expr_from_text(&if exclusive { + format!("&raw mut {expr}") + } else { + format!("&raw const {expr}") + }) +} pub fn expr_reborrow(expr: ast::Expr) -> ast::Expr { expr_from_text(&format!("&mut *{expr}")) } diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs index 969552392180..32fc1e6fedd4 100644 --- a/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -566,6 +566,22 @@ impl SyntaxFactory { ast.into() } + pub fn expr_raw_ref(&self, expr: ast::Expr, exclusive: bool) -> ast::Expr { + let ast::Expr::RefExpr(ast) = + make::expr_raw_ref(expr.clone(), exclusive).clone_for_update() + else { + unreachable!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(expr.syntax().clone(), ast.expr().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast.into() + } + pub fn expr_closure( &self, pats: impl IntoIterator,