Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 214 additions & 0 deletions crates/ide-assists/src/handlers/add_explicit_dot_deref.rs
Original file line number Diff line number Diff line change
@@ -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();
}"#,
);
}
}
2 changes: 2 additions & 0 deletions crates/ide-assists/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions crates/ide-assists/src/tests/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
7 changes: 7 additions & 0 deletions crates/syntax/src/ast/make.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}"))
}
Expand Down
16 changes: 16 additions & 0 deletions crates/syntax/src/ast/syntax_factory/constructors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = ast::Param>,
Expand Down