Add generate_impl_trait for generate_impl

This commit is contained in:
A4-Tacks 2025-06-06 16:09:45 +08:00
parent 0901d513d8
commit f345b76895
No known key found for this signature in database
GPG key ID: 86AC1F526BA06668
3 changed files with 294 additions and 2 deletions

View file

@ -1,12 +1,17 @@
use syntax::{
ast::{self, AstNode, HasName, edit_in_place::Indent, make},
ast::{self, AstNode, HasGenericParams, HasName, edit_in_place::Indent, make},
syntax_editor::{Position, SyntaxEditor},
};
use crate::{AssistContext, AssistId, Assists, utils};
use crate::{
AssistContext, AssistId, Assists,
utils::{self, DefaultMethods, IgnoreAssocItems},
};
fn insert_impl(editor: &mut SyntaxEditor, impl_: &ast::Impl, nominal: &ast::Adt) {
let indent = nominal.indent_level();
impl_.indent(indent);
editor.insert_all(
Position::after(nominal.syntax()),
vec![
@ -120,6 +125,101 @@ pub(crate) fn generate_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) ->
)
}
// Assist: generate_impl_trait
//
// Adds this trait impl for a type.
//
// ```
// trait $0Foo {
// fn foo(&self) -> i32;
// }
// ```
// ->
// ```
// trait Foo {
// fn foo(&self) -> i32;
// }
//
// impl Foo for ${1:_} {
// $0fn foo(&self) -> i32 {
// todo!()
// }
// }
// ```
pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let name = ctx.find_node_at_offset::<ast::Name>()?;
let trait_ = ast::Trait::cast(name.syntax().parent()?)?;
let target_scope = ctx.sema.scope(trait_.syntax())?;
let hir_trait = ctx.sema.to_def(&trait_)?;
let target = trait_.syntax().text_range();
acc.add(
AssistId::generate("generate_impl_trait"),
format!("Generate `{name}` impl for type"),
target,
|edit| {
let holder_arg = ast::GenericArg::TypeArg(make::type_arg(make::ty_placeholder()));
let missing_items = utils::filter_assoc_items(
&ctx.sema,
&hir_trait.items(ctx.db()),
DefaultMethods::No,
IgnoreAssocItems::DocHiddenAttrPresent,
);
let impl_ = make::impl_trait(
trait_.unsafe_token().is_some(),
None,
trait_.generic_param_list().map(|list| {
make::generic_arg_list(list.generic_params().map(|_| holder_arg.clone()))
}),
None,
None,
false,
make::ty(&name.text()),
make::ty_placeholder(),
None,
None,
None,
)
.clone_for_update();
let trait_ = edit.make_mut(trait_);
if !missing_items.is_empty() {
utils::add_trait_assoc_items_to_impl(
&ctx.sema,
ctx.config,
&missing_items,
hir_trait,
&impl_,
&target_scope,
);
}
if let Some(cap) = ctx.config.snippet_cap {
if let Some(generics) = impl_.trait_().and_then(|it| it.generic_arg_list()) {
for generic in generics.generic_args() {
edit.add_placeholder_snippet(cap, generic);
}
}
if let Some(ty) = impl_.self_ty() {
edit.add_placeholder_snippet(cap, ty);
}
if let Some(item) = impl_.assoc_item_list().and_then(|it| it.assoc_items().next()) {
edit.add_tabstop_before(cap, item);
} else if let Some(l_curly) =
impl_.assoc_item_list().and_then(|it| it.l_curly_token())
{
edit.add_tabstop_after_token(cap, l_curly);
}
}
insert_impl(impl_, &trait_);
},
)
}
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_target};
@ -492,4 +592,172 @@ mod tests {
"#,
);
}
#[test]
fn test_add_impl_trait() {
check_assist(
generate_impl_trait,
r#"
trait $0Foo {
fn foo(&self) -> i32;
fn bar(&self) -> i32 {
self.foo()
}
}
"#,
r#"
trait Foo {
fn foo(&self) -> i32;
fn bar(&self) -> i32 {
self.foo()
}
}
impl Foo for ${1:_} {
$0fn foo(&self) -> i32 {
todo!()
}
}
"#,
);
}
#[test]
fn test_add_impl_trait_use_generic() {
check_assist(
generate_impl_trait,
r#"
trait $0Foo<T> {
fn foo(&self) -> T;
fn bar(&self) -> T {
self.foo()
}
}
"#,
r#"
trait Foo<T> {
fn foo(&self) -> T;
fn bar(&self) -> T {
self.foo()
}
}
impl Foo<${1:_}> for ${2:_} {
$0fn foo(&self) -> _ {
todo!()
}
}
"#,
);
check_assist(
generate_impl_trait,
r#"
trait $0Foo<T, U> {
fn foo(&self) -> T;
fn bar(&self) -> T {
self.foo()
}
}
"#,
r#"
trait Foo<T, U> {
fn foo(&self) -> T;
fn bar(&self) -> T {
self.foo()
}
}
impl Foo<${1:_}, ${2:_}> for ${3:_} {
$0fn foo(&self) -> _ {
todo!()
}
}
"#,
);
}
#[test]
fn test_add_impl_trait_docs() {
check_assist(
generate_impl_trait,
r#"
/// foo
trait $0Foo {
/// foo method
fn foo(&self) -> i32;
fn bar(&self) -> i32 {
self.foo()
}
}
"#,
r#"
/// foo
trait Foo {
/// foo method
fn foo(&self) -> i32;
fn bar(&self) -> i32 {
self.foo()
}
}
impl Foo for ${1:_} {
$0fn foo(&self) -> i32 {
todo!()
}
}
"#,
);
}
#[test]
fn test_add_impl_trait_assoc_types() {
check_assist(
generate_impl_trait,
r#"
trait $0Foo {
type Output;
fn foo(&self) -> Self::Output;
}
"#,
r#"
trait Foo {
type Output;
fn foo(&self) -> Self::Output;
}
impl Foo for ${1:_} {
$0type Output;
fn foo(&self) -> Self::Output {
todo!()
}
}
"#,
);
}
#[test]
fn test_add_impl_trait_empty() {
check_assist(
generate_impl_trait,
r#"
trait $0Foo {}
"#,
r#"
trait Foo {}
impl Foo for ${1:_} {$0}
"#,
);
}
}

View file

@ -301,6 +301,7 @@ mod handlers {
generate_function::generate_function,
generate_impl::generate_impl,
generate_impl::generate_trait_impl,
generate_impl::generate_impl_trait,
generate_is_empty_from_len::generate_is_empty_from_len,
generate_mut_trait_impl::generate_mut_trait_impl,
generate_new::generate_new,

View file

@ -1880,6 +1880,29 @@ impl<T: Clone> Ctx<T> {$0}
)
}
#[test]
fn doctest_generate_impl_trait() {
check_doc_test(
"generate_impl_trait",
r#####"
trait $0Foo {
fn foo(&self) -> i32;
}
"#####,
r#####"
trait Foo {
fn foo(&self) -> i32;
}
impl Foo for ${1:_} {
$0fn foo(&self) -> i32 {
todo!()
}
}
"#####,
)
}
#[test]
fn doctest_generate_is_empty_from_len() {
check_doc_test(