From 3e5df7268e030cb2af9b43de2f849593d108eb89 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 23 Jun 2025 11:42:19 +0800 Subject: [PATCH 1/3] Add ide-assist; Generate AsRef impl from Borrow --- .../generate_asref_impl_from_borrow.rs | 251 ++++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/ide-assists/src/tests/generated.rs | 62 +++++ 3 files changed, 315 insertions(+) create mode 100644 crates/ide-assists/src/handlers/generate_asref_impl_from_borrow.rs diff --git a/crates/ide-assists/src/handlers/generate_asref_impl_from_borrow.rs b/crates/ide-assists/src/handlers/generate_asref_impl_from_borrow.rs new file mode 100644 index 000000000000..699a122329a4 --- /dev/null +++ b/crates/ide-assists/src/handlers/generate_asref_impl_from_borrow.rs @@ -0,0 +1,251 @@ +use syntax::ast::edit_in_place::Indent; +use syntax::ast::make; +use syntax::ast::{self, AstNode, HasName}; +use syntax::ted; + +use crate::{AssistContext, AssistId, Assists}; + +// Assist: generate_asref_impl_from_borrow +// +// Generate `AsRef` implement from `Borrow`. +// +// ``` +// struct Foo(T); +// +// impl $0Borrow for Foo { +// fn borrow(&self) -> &T { +// &self.0 +// } +// } +// ``` +// -> +// ``` +// struct Foo(T); +// +// $0impl AsRef for Foo { +// fn as_ref(&self) -> &T { +// &self.0 +// } +// } +// +// impl Borrow for Foo { +// fn borrow(&self) -> &T { +// &self.0 +// } +// } +// ``` +// +// --- +// Generate `AsMut` implement from `BorrowMut`. +// +// ``` +// struct Foo(T); +// +// impl $0BorrowMut for Foo { +// fn borrow_mut(&mut self) -> &mut T { +// &mut self.0 +// } +// } +// ``` +// -> +// ``` +// struct Foo(T); +// +// $0impl AsMut for Foo { +// fn as_mut(&mut self) -> &mut T { +// &mut self.0 +// } +// } +// +// impl BorrowMut for Foo { +// fn borrow_mut(&mut self) -> &mut T { +// &mut self.0 +// } +// } +// ``` +pub(crate) fn generate_asref_impl_from_borrow( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + let ty = ctx.find_node_at_offset::()?; + let impl_ = ast::Impl::cast(ty.syntax().parent()?)?.clone_for_update(); + let path = ast::PathType::cast(impl_.trait_()?.syntax().clone())?; + let indent = impl_.indent_level(); + + let name = path.path()?.segment()?.name_ref()?; + + let (target_name, target_method_name) = match &*name.text() { + "Borrow" => ("AsRef", "as_ref"), + "BorrowMut" => ("AsMut", "as_mut"), + _ => return None, + }; + + let method = impl_.assoc_item_list()?.assoc_items().find_map(|it| match it { + ast::AssocItem::Fn(f) => Some(f), + _ => None, + })?; + + let target = impl_.syntax().text_range(); + acc.add( + AssistId::generate("generate_asref_impl_from_borrow"), + format!("Generate `{target_name}` implement from `{name}`"), + target, + |edit| { + ted::replace(name.syntax(), make::name_ref(target_name).syntax().clone_for_update()); + + if let Some(name) = method.name() { + ted::replace( + name.syntax(), + make::name(target_method_name).syntax().clone_for_update(), + ); + } + + edit.insert(target.start(), format!("$0{impl_}\n\n{indent}")); + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist; + + use super::*; + + #[test] + fn test_generate_asref_impl_from_borrow() { + check_assist( + generate_asref_impl_from_borrow, + r#" +struct Foo(T); + +impl $0Borrow for Foo { + fn borrow(&self) -> &T { + &self.0 + } +} + "#, + r#" +struct Foo(T); + +$0impl AsRef for Foo { + fn as_ref(&self) -> &T { + &self.0 + } +} + +impl Borrow for Foo { + fn borrow(&self) -> &T { + &self.0 + } +} + "#, + ); + } + + #[test] + fn test_generate_asmut_impl_from_borrow_mut() { + check_assist( + generate_asref_impl_from_borrow, + r#" +struct Foo(T); + +impl $0BorrowMut for Foo { + fn borrow_mut(&mut self) -> &mut T { + &mut self.0 + } +} + "#, + r#" +struct Foo(T); + +$0impl AsMut for Foo { + fn as_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl BorrowMut for Foo { + fn borrow_mut(&mut self) -> &mut T { + &mut self.0 + } +} + "#, + ); + } + + #[test] + fn test_generate_asref_impl_from_borrow_attributes() { + check_assist( + generate_asref_impl_from_borrow, + r#" +struct Foo(T); + +#[cfg(feature = "foo")] +impl $0Borrow for Foo { + /// some docs + fn borrow(&self) -> &T { + &self.0 + } +} + "#, + r#" +struct Foo(T); + +$0#[cfg(feature = "foo")] +impl AsRef for Foo { + /// some docs + fn as_ref(&self) -> &T { + &self.0 + } +} + +#[cfg(feature = "foo")] +impl Borrow for Foo { + /// some docs + fn borrow(&self) -> &T { + &self.0 + } +} + "#, + ); + } + + #[test] + fn test_generate_asref_impl_from_borrow_indent() { + check_assist( + generate_asref_impl_from_borrow, + r#" +mod foo { + mod bar { + struct Foo(T); + + impl $0Borrow for Foo { + fn borrow(&self) -> &T { + &self.0 + } + } + } +} + "#, + r#" +mod foo { + mod bar { + struct Foo(T); + + $0impl AsRef for Foo { + fn as_ref(&self) -> &T { + &self.0 + } + } + + impl Borrow for Foo { + fn borrow(&self) -> &T { + &self.0 + } + } + } +} + "#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index c2604432032d..3467852831e7 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -153,6 +153,7 @@ mod handlers { mod flip_comma; mod flip_or_pattern; mod flip_trait_bound; + mod generate_asref_impl_from_borrow; mod generate_constant; mod generate_default_from_enum_variant; mod generate_default_from_new; @@ -302,6 +303,7 @@ mod handlers { generate_impl::generate_impl, generate_impl::generate_trait_impl, generate_is_empty_from_len::generate_is_empty_from_len, + generate_asref_impl_from_borrow::generate_asref_impl_from_borrow, generate_mut_trait_impl::generate_mut_trait_impl, generate_new::generate_new, generate_trait_from_impl::generate_trait_from_impl, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index fe74694d8a44..df6849012ccd 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1311,6 +1311,68 @@ fn foo() { } ) } +#[test] +fn doctest_generate_asref_impl_from_borrow() { + check_doc_test( + "generate_asref_impl_from_borrow", + r#####" +struct Foo(T); + +impl $0Borrow for Foo { + fn borrow(&self) -> &T { + &self.0 + } +} +"#####, + r#####" +struct Foo(T); + +$0impl AsRef for Foo { + fn as_ref(&self) -> &T { + &self.0 + } +} + +impl Borrow for Foo { + fn borrow(&self) -> &T { + &self.0 + } +} +"#####, + ) +} + +#[test] +fn doctest_generate_asref_impl_from_borrow_1() { + check_doc_test( + "generate_asref_impl_from_borrow", + r#####" +struct Foo(T); + +impl $0BorrowMut for Foo { + fn borrow_mut(&mut self) -> &mut T { + &mut self.0 + } +} +"#####, + r#####" +struct Foo(T); + +$0impl AsMut for Foo { + fn as_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl BorrowMut for Foo { + fn borrow_mut(&mut self) -> &mut T { + &mut self.0 + } +} +"#####, + ) +} + #[test] fn doctest_generate_constant() { check_doc_test( From 0097bd8dcd6586f06e64352f97efd87107732019 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 29 Jun 2025 17:31:11 +0800 Subject: [PATCH 2/3] Use SyntaxEditor instead of ted --- .../generate_asref_impl_from_borrow.rs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_asref_impl_from_borrow.rs b/crates/ide-assists/src/handlers/generate_asref_impl_from_borrow.rs index 699a122329a4..aad9da92865a 100644 --- a/crates/ide-assists/src/handlers/generate_asref_impl_from_borrow.rs +++ b/crates/ide-assists/src/handlers/generate_asref_impl_from_borrow.rs @@ -1,7 +1,7 @@ -use syntax::ast::edit_in_place::Indent; -use syntax::ast::make; -use syntax::ast::{self, AstNode, HasName}; -use syntax::ted; +use syntax::{ + ast::{self, AstNode, HasName, edit_in_place::Indent, make}, + syntax_editor::Position, +}; use crate::{AssistContext, AssistId, Assists}; @@ -68,7 +68,7 @@ pub(crate) fn generate_asref_impl_from_borrow( ctx: &AssistContext<'_>, ) -> Option<()> { let ty = ctx.find_node_at_offset::()?; - let impl_ = ast::Impl::cast(ty.syntax().parent()?)?.clone_for_update(); + let impl_ = ast::Impl::cast(ty.syntax().parent()?)?; let path = ast::PathType::cast(impl_.trait_()?.syntax().clone())?; let indent = impl_.indent_level(); @@ -91,16 +91,29 @@ pub(crate) fn generate_asref_impl_from_borrow( format!("Generate `{target_name}` implement from `{name}`"), target, |edit| { - ted::replace(name.syntax(), make::name_ref(target_name).syntax().clone_for_update()); + let mut editor = edit.make_editor(impl_.syntax()); + editor.replace(name.syntax(), make::name_ref(target_name).syntax().clone_for_update()); if let Some(name) = method.name() { - ted::replace( + editor.replace( name.syntax(), make::name(target_method_name).syntax().clone_for_update(), ); } - edit.insert(target.start(), format!("$0{impl_}\n\n{indent}")); + editor.insert_all( + Position::after(impl_.syntax()), + vec![ + make::tokens::whitespace(&format!("\n\n{indent}")).into(), + impl_.syntax().clone_for_update().into(), + ], + ); + + if let Some(cap) = ctx.config.snippet_cap { + edit.add_tabstop_before(cap, impl_); + } + + edit.add_file_edits(ctx.vfs_file_id(), editor); }, ) } From d29eb4cb9abb1327c863ae7cc3c1d2d176e5adc8 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 18 Jul 2025 05:26:53 +0800 Subject: [PATCH 3/3] Use hir instead of trait name --- .../generate_asref_impl_from_borrow.rs | 41 ++++++++++++++++--- crates/ide-assists/src/tests/generated.rs | 6 +++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_asref_impl_from_borrow.rs b/crates/ide-assists/src/handlers/generate_asref_impl_from_borrow.rs index aad9da92865a..7752af2913bd 100644 --- a/crates/ide-assists/src/handlers/generate_asref_impl_from_borrow.rs +++ b/crates/ide-assists/src/handlers/generate_asref_impl_from_borrow.rs @@ -1,3 +1,4 @@ +use ide_db::{famous_defs::FamousDefs, traits::resolve_target_trait}; use syntax::{ ast::{self, AstNode, HasName, edit_in_place::Indent, make}, syntax_editor::Position, @@ -10,6 +11,8 @@ use crate::{AssistContext, AssistId, Assists}; // Generate `AsRef` implement from `Borrow`. // // ``` +// //- minicore: borrow, as_ref +// use core::borrow::Borrow; // struct Foo(T); // // impl $0Borrow for Foo { @@ -20,6 +23,7 @@ use crate::{AssistContext, AssistId, Assists}; // ``` // -> // ``` +// use core::borrow::Borrow; // struct Foo(T); // // $0impl AsRef for Foo { @@ -39,6 +43,8 @@ use crate::{AssistContext, AssistId, Assists}; // Generate `AsMut` implement from `BorrowMut`. // // ``` +// //- minicore: borrow_mut, as_mut +// use core::borrow::BorrowMut; // struct Foo(T); // // impl $0BorrowMut for Foo { @@ -49,6 +55,7 @@ use crate::{AssistContext, AssistId, Assists}; // ``` // -> // ``` +// use core::borrow::BorrowMut; // struct Foo(T); // // $0impl AsMut for Foo { @@ -73,12 +80,11 @@ pub(crate) fn generate_asref_impl_from_borrow( let indent = impl_.indent_level(); let name = path.path()?.segment()?.name_ref()?; + let scope = ctx.sema.scope(path.syntax())?; + let famous = FamousDefs(&ctx.sema, scope.krate()); + let trait_ = resolve_target_trait(&ctx.sema, &impl_)?; - let (target_name, target_method_name) = match &*name.text() { - "Borrow" => ("AsRef", "as_ref"), - "BorrowMut" => ("AsMut", "as_mut"), - _ => return None, - }; + let (target_name, target_method_name) = out_trait(famous, trait_)?; let method = impl_.assoc_item_list()?.assoc_items().find_map(|it| match it { ast::AssocItem::Fn(f) => Some(f), @@ -118,6 +124,19 @@ pub(crate) fn generate_asref_impl_from_borrow( ) } +fn out_trait( + famous: FamousDefs<'_, '_>, + trait_: hir::Trait, +) -> Option<(&'static str, &'static str)> { + if trait_ == famous.core_borrow_Borrow()? { + Some(("AsRef", "as_ref")) + } else if trait_ == famous.core_borrow_BorrowMut()? { + Some(("AsMut", "as_mut")) + } else { + None + } +} + #[cfg(test)] mod tests { use crate::tests::check_assist; @@ -129,6 +148,8 @@ mod tests { check_assist( generate_asref_impl_from_borrow, r#" +//- minicore: borrow, as_ref +use core::borrow::Borrow; struct Foo(T); impl $0Borrow for Foo { @@ -138,6 +159,7 @@ impl $0Borrow for Foo { } "#, r#" +use core::borrow::Borrow; struct Foo(T); $0impl AsRef for Foo { @@ -160,6 +182,8 @@ impl Borrow for Foo { check_assist( generate_asref_impl_from_borrow, r#" +//- minicore: borrow_mut, as_mut +use core::borrow::BorrowMut; struct Foo(T); impl $0BorrowMut for Foo { @@ -169,6 +193,7 @@ impl $0BorrowMut for Foo { } "#, r#" +use core::borrow::BorrowMut; struct Foo(T); $0impl AsMut for Foo { @@ -191,6 +216,8 @@ impl BorrowMut for Foo { check_assist( generate_asref_impl_from_borrow, r#" +//- minicore: borrow, as_ref +use core::borrow::Borrow; struct Foo(T); #[cfg(feature = "foo")] @@ -202,6 +229,7 @@ impl $0Borrow for Foo { } "#, r#" +use core::borrow::Borrow; struct Foo(T); $0#[cfg(feature = "foo")] @@ -228,8 +256,10 @@ impl Borrow for Foo { check_assist( generate_asref_impl_from_borrow, r#" +//- minicore: borrow, as_ref mod foo { mod bar { + use core::borrow::Borrow; struct Foo(T); impl $0Borrow for Foo { @@ -243,6 +273,7 @@ mod foo { r#" mod foo { mod bar { + use core::borrow::Borrow; struct Foo(T); $0impl AsRef for Foo { diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index df6849012ccd..0a5dcdddcceb 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1316,6 +1316,8 @@ fn doctest_generate_asref_impl_from_borrow() { check_doc_test( "generate_asref_impl_from_borrow", r#####" +//- minicore: borrow, as_ref +use core::borrow::Borrow; struct Foo(T); impl $0Borrow for Foo { @@ -1325,6 +1327,7 @@ impl $0Borrow for Foo { } "#####, r#####" +use core::borrow::Borrow; struct Foo(T); $0impl AsRef for Foo { @@ -1347,6 +1350,8 @@ fn doctest_generate_asref_impl_from_borrow_1() { check_doc_test( "generate_asref_impl_from_borrow", r#####" +//- minicore: borrow_mut, as_mut +use core::borrow::BorrowMut; struct Foo(T); impl $0BorrowMut for Foo { @@ -1356,6 +1361,7 @@ impl $0BorrowMut for Foo { } "#####, r#####" +use core::borrow::BorrowMut; struct Foo(T); $0impl AsMut for Foo {