Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/add_explicit_dot_deref.rs')
| -rw-r--r-- | crates/ide-assists/src/handlers/add_explicit_dot_deref.rs | 214 |
1 files changed, 214 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(); + }"#, + ); + } +} |