Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #20996 from A4-Tacks/explicit-dot-call-deref
Add ide-assist: add_explicit_method_call_deref
Lukas Wirth 4 months ago
parent 10418b4 · parent d53146a · commit 3aecf08
-rw-r--r--crates/ide-assists/src/handlers/add_explicit_dot_deref.rs214
-rw-r--r--crates/ide-assists/src/lib.rs2
-rw-r--r--crates/ide-assists/src/tests/generated.rs21
-rw-r--r--crates/syntax/src/ast/make.rs7
-rw-r--r--crates/syntax/src/ast/syntax_factory/constructors.rs16
5 files changed, 260 insertions, 0 deletions
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 0000000000..d27a6b4ce7
--- /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<AdjustKind> {
+ 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 47cb4c8e74..80f05caf4e 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;
@@ -242,6 +243,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 7eef257b95..581efd0bac 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -70,6 +70,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(
"add_explicit_type",
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index b2904ce3c0..98d759aef2 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 aca6fcfb2e..7cf9e2bf14 100644
--- a/crates/syntax/src/ast/syntax_factory/constructors.rs
+++ b/crates/syntax/src/ast/syntax_factory/constructors.rs
@@ -748,6 +748,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<Item = ast::Param>,