From 677151e4e128fc14848d03e7c092bbdc2c7da66a Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Sat, 24 Jun 2023 02:08:02 +0200 Subject: [PATCH 1/5] Version 1 --- .../src/handlers/generate_trait_from_impl.rs | 343 ++++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/ide-assists/src/tests/generated.rs | 56 +++ crates/syntax/src/ast/make.rs | 41 +++ 4 files changed, 442 insertions(+) create mode 100644 crates/ide-assists/src/handlers/generate_trait_from_impl.rs diff --git a/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs new file mode 100644 index 00000000000..55a3b7f442f --- /dev/null +++ b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -0,0 +1,343 @@ +use crate::assist_context::{AssistContext, Assists}; +use ide_db::{assists::AssistId, SnippetCap}; +use syntax::{ + ast::{self, HasGenericParams, HasVisibility}, + AstNode, +}; + +// NOTES : +// We generate erroneous code if a function is declared const (E0379) +// This is left to the user to correct as our only option is to remove the +// function completely which we should not be doing. + +// Assist: generate_trait_from_impl +// +// Generate trait for an already defined inherent impl and convert impl to a trait impl. +// +// ``` +// struct Foo([i32; N]); +// +// macro_rules! const_maker { +// ($t:ty, $v:tt) => { +// const CONST: $t = $v; +// }; +// } +// +// impl Fo$0o { +// // Used as an associated constant. +// const CONST_ASSOC: usize = N * 4; +// +// fn create() -> Option<()> { +// Some(()) +// } +// +// const_maker! {i32, 7} +// } +// ``` +// -> +// ``` +// struct Foo([i32; N]); +// +// macro_rules! const_maker { +// ($t:ty, $v:tt) => { +// const CONST: $t = $v; +// }; +// } +// +// trait NewTrait { +// // Used as an associated constant. +// const CONST_ASSOC: usize = N * 4; +// +// fn create() -> Option<()>; +// +// const_maker! {i32, 7} +// } +// +// impl NewTrait for Foo { +// // Used as an associated constant. +// const CONST_ASSOC: usize = N * 4; +// +// fn create() -> Option<()> { +// Some(()) +// } +// +// const_maker! {i32, 7} +// } +// ``` +pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + // Get AST Node + let impl_ast = ctx.find_node_at_offset::()?; + + // If impl is not inherent then we don't really need to go any further. + if impl_ast.for_token().is_some() { + return None; + } + + let assoc_items = impl_ast.assoc_item_list(); + if assoc_items.is_none() { + // Also do not do anything if no assoc item is there. + return None; + } + + let assoc_items = assoc_items.unwrap(); + let first_element = assoc_items.assoc_items().next(); + if first_element.is_none() { + // No reason for an assist. + return None; + } + + acc.add( + AssistId("generate_trait_from_impl", ide_db::assists::AssistKind::Generate), + "Generate trait from impl".to_owned(), + impl_ast.syntax().text_range(), + |builder| { + let trait_items = assoc_items.clone_for_update(); + let impl_items = assoc_items.clone_for_update(); + + trait_items.assoc_items().for_each(|item| { + strip_body(&item); + remove_items_visibility(&item); + }); + + syntax::ted::replace(assoc_items.clone_for_update().syntax(), impl_items.syntax()); + + impl_items.assoc_items().for_each(|item| { + remove_items_visibility(&item); + }); + + let trait_ast = ast::make::trait_( + false, + "NewTrait".to_string(), + HasGenericParams::generic_param_list(&impl_ast), + HasGenericParams::where_clause(&impl_ast), + trait_items, + ); + + // Change `impl Foo` to `impl NewTrait for Foo` + // First find the PATH_TYPE which is what Foo is. + let impl_name = impl_ast.self_ty().unwrap(); + let trait_name = if let Some(genpars) = impl_ast.generic_param_list() { + format!("NewTrait{}", genpars.to_generic_args()) + } else { + format!("NewTrait") + }; + + // // Then replace + builder.replace( + impl_name.clone().syntax().text_range(), + format!("{} for {}", trait_name, impl_name.to_string()), + ); + + builder.replace( + impl_ast.assoc_item_list().unwrap().syntax().text_range(), + impl_items.to_string(), + ); + + // Insert trait before TraitImpl + builder.insert_snippet( + SnippetCap::new(true).unwrap(), + impl_ast.syntax().text_range().start(), + format!("{}\n\n", trait_ast.to_string()), + ); + }, + ); + + Some(()) +} + +/// `E0449` Trait items always share the visibility of their trait +fn remove_items_visibility(item: &ast::AssocItem) { + match item { + ast::AssocItem::Const(c) => { + if let Some(vis) = c.visibility() { + syntax::ted::remove(vis.syntax()); + } + } + ast::AssocItem::Fn(f) => { + if let Some(vis) = f.visibility() { + syntax::ted::remove(vis.syntax()); + } + } + ast::AssocItem::TypeAlias(t) => { + if let Some(vis) = t.visibility() { + syntax::ted::remove(vis.syntax()); + } + } + _ => (), + } +} + +fn strip_body(item: &ast::AssocItem) { + match item { + ast::AssocItem::Fn(f) => { + if let Some(body) = f.body() { + // In constrast to function bodies, we want to see no ws before a semicolon. + // So let's remove them if we see any. + if let Some(prev) = body.syntax().prev_sibling_or_token() { + if prev.kind() == syntax::SyntaxKind::WHITESPACE { + syntax::ted::remove(prev); + } + } + + syntax::ted::replace(body.syntax(), ast::make::tokens::semicolon()); + } + } + _ => (), + }; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{check_assist, check_assist_not_applicable}; + + #[test] + fn test_assoc_item_fn() { + check_assist( + generate_trait_from_impl, + r#" +struct Foo(f64); + +impl F$0oo { + fn add(&mut self, x: f64) { + self.0 += x; + } +}"#, + r#" +struct Foo(f64); + +trait NewTrait { + fn add(&mut self, x: f64); +} + +impl NewTrait for Foo { + fn add(&mut self, x: f64) { + self.0 += x; + } +}"#, + ) + } + + #[test] + fn test_assoc_item_macro() { + check_assist( + generate_trait_from_impl, + r#" +struct Foo; + +macro_rules! const_maker { + ($t:ty, $v:tt) => { + const CONST: $t = $v; + }; +} + +impl F$0oo { + const_maker! {i32, 7} +}"#, + r#" +struct Foo; + +macro_rules! const_maker { + ($t:ty, $v:tt) => { + const CONST: $t = $v; + }; +} + +trait NewTrait { + const_maker! {i32, 7} +} + +impl NewTrait for Foo { + const_maker! {i32, 7} +}"#, + ) + } + + #[test] + fn test_assoc_item_const() { + check_assist( + generate_trait_from_impl, + r#" +struct Foo; + +impl F$0oo { + const ABC: i32 = 3; +}"#, + r#" +struct Foo; + +trait NewTrait { + const ABC: i32 = 3; +} + +impl NewTrait for Foo { + const ABC: i32 = 3; +}"#, + ) + } + + #[test] + fn test_impl_with_generics() { + check_assist( + generate_trait_from_impl, + r#" +struct Foo([i32; N]); + +impl F$0oo { + // Used as an associated constant. + const CONST: usize = N * 4; +} + "#, + r#" +struct Foo([i32; N]); + +trait NewTrait { + // Used as an associated constant. + const CONST: usize = N * 4; +} + +impl NewTrait for Foo { + // Used as an associated constant. + const CONST: usize = N * 4; +} + "#, + ) + } + + #[test] + fn test_e0449_avoided() { + check_assist( + generate_trait_from_impl, + r#" +struct Foo; + +impl F$0oo { + pub fn a_func() -> Option<()> { + Some(()) + } +}"#, + r#" +struct Foo; + +trait NewTrait { + fn a_func() -> Option<()>; +} + +impl NewTrait for Foo { + fn a_func() -> Option<()> { + Some(()) + } +}"#, + ) + } + + #[test] + fn test_empty_inherent_impl() { + check_assist_not_applicable( + generate_trait_from_impl, + r#" +impl Emp$0tyImpl{} +"#, + ) + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index dc0a69971ef..16affe3e559 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -160,6 +160,7 @@ mod handlers { mod generate_new; mod generate_setter; mod generate_delegate_methods; + mod generate_trait_from_impl; mod add_return_type; mod inline_call; mod inline_const_as_literal; @@ -266,6 +267,7 @@ mod handlers { generate_impl::generate_trait_impl, generate_is_empty_from_len::generate_is_empty_from_len, generate_new::generate_new, + generate_trait_from_impl::generate_trait_from_impl, inline_call::inline_call, inline_call::inline_into_callers, inline_const_as_literal::inline_const_as_literal, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index c6c70cc64a6..2404fecc452 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1500,6 +1500,62 @@ impl Person { ) } +#[test] +fn doctest_generate_trait_from_impl() { + check_doc_test( + "generate_trait_from_impl", + r#####" +struct Foo([i32; N]); + +macro_rules! const_maker { + ($t:ty, $v:tt) => { + const CONST: $t = $v; + }; +} + +impl Fo$0o { + // Used as an associated constant. + const CONST_ASSOC: usize = N * 4; + + fn create() -> Option<()> { + Some(()) + } + + const_maker! {i32, 7} +} +"#####, + r#####" +struct Foo([i32; N]); + +macro_rules! const_maker { + ($t:ty, $v:tt) => { + const CONST: $t = $v; + }; +} + +trait NewTrait { + // Used as an associated constant. + const CONST_ASSOC: usize = N * 4; + + fn create() -> Option<()>; + + const_maker! {i32, 7} +} + +impl NewTrait for Foo { + // Used as an associated constant. + const CONST_ASSOC: usize = N * 4; + + fn create() -> Option<()> { + Some(()) + } + + const_maker! {i32, 7} +} +"#####, + ) +} + #[test] fn doctest_generate_trait_impl() { check_doc_test( diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index e435766000e..1675d1af1dd 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -863,6 +863,36 @@ pub fn param_list( ast_from_text(&list) } +pub fn trait_( + is_unsafe: bool, + ident: String, + gen_params: Option, + where_clause: Option, + assoc_items: ast::AssocItemList, +) -> ast::Trait { + let mut text = String::new(); + + if is_unsafe { + format_to!(text, "unsafe "); + } + + format_to!(text, "trait {ident}"); + + if let Some(gen_params) = gen_params { + format_to!(text, "{} ", gen_params.to_string()); + } else { + text.push(' '); + } + + if let Some(where_clause) = where_clause { + format_to!(text, "{} ", where_clause.to_string()); + } + + format_to!(text, "{}", assoc_items.to_string()); + + ast_from_text(&text) +} + pub fn type_bound(bound: &str) -> ast::TypeBound { ast_from_text(&format!("fn f() {{ }}")) } @@ -1037,6 +1067,17 @@ pub mod tokens { ) }); + pub fn semicolon() -> SyntaxToken { + SOURCE_FILE + .tree() + .syntax() + .clone_for_update() + .descendants_with_tokens() + .filter_map(|it| it.into_token()) + .find(|it| it.kind() == SEMICOLON) + .unwrap() + } + pub fn single_space() -> SyntaxToken { SOURCE_FILE .tree() From 20c877a700264e01a85cbd27709f3644853e50cf Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 28 Jun 2023 12:25:31 +0200 Subject: [PATCH 2/5] Update crates/ide-assists/src/handlers/generate_trait_from_impl.rs Co-authored-by: Lukas Wirth --- .../ide-assists/src/handlers/generate_trait_from_impl.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs index 55a3b7f442f..ce9eeb82007 100644 --- a/crates/ide-assists/src/handlers/generate_trait_from_impl.rs +++ b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -73,13 +73,7 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ return None; } - let assoc_items = impl_ast.assoc_item_list(); - if assoc_items.is_none() { - // Also do not do anything if no assoc item is there. - return None; - } - - let assoc_items = assoc_items.unwrap(); + let assoc_items = impl_ast.assoc_item_list()?; let first_element = assoc_items.assoc_items().next(); if first_element.is_none() { // No reason for an assist. From 03423116ad533e1e80d7095595429e7cb0af2af7 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Tue, 4 Jul 2023 17:13:07 +0200 Subject: [PATCH 3/5] Generate trait from impl v2 --- .../src/handlers/generate_trait_from_impl.rs | 108 +++++++++++++----- crates/syntax/src/ast/make.rs | 2 +- 2 files changed, 79 insertions(+), 31 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs index ce9eeb82007..d3192ae4091 100644 --- a/crates/ide-assists/src/handlers/generate_trait_from_impl.rs +++ b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -1,8 +1,8 @@ use crate::assist_context::{AssistContext, Assists}; -use ide_db::{assists::AssistId, SnippetCap}; +use ide_db::assists::AssistId; use syntax::{ - ast::{self, HasGenericParams, HasVisibility}, - AstNode, + ast::{self, edit::IndentLevel, make, HasGenericParams, HasVisibility}, + ted, AstNode, SyntaxKind, }; // NOTES : @@ -68,6 +68,16 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ // Get AST Node let impl_ast = ctx.find_node_at_offset::()?; + // Check if cursor is to the left of assoc item list's L_CURLY. + // if no L_CURLY then return. + let l_curly = impl_ast.assoc_item_list()?.l_curly_token()?; + + let cursor_offset = ctx.offset(); + let l_curly_offset = l_curly.text_range(); + if cursor_offset >= l_curly_offset.start() { + return None; + } + // If impl is not inherent then we don't really need to go any further. if impl_ast.for_token().is_some() { return None; @@ -80,9 +90,11 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ return None; } + let impl_name = impl_ast.self_ty()?; + acc.add( AssistId("generate_trait_from_impl", ide_db::assists::AssistKind::Generate), - "Generate trait from impl".to_owned(), + "Generate trait from impl", impl_ast.syntax().text_range(), |builder| { let trait_items = assoc_items.clone_for_update(); @@ -93,45 +105,43 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ remove_items_visibility(&item); }); - syntax::ted::replace(assoc_items.clone_for_update().syntax(), impl_items.syntax()); + ted::replace(assoc_items.clone_for_update().syntax(), impl_items.syntax()); impl_items.assoc_items().for_each(|item| { remove_items_visibility(&item); }); - let trait_ast = ast::make::trait_( + let trait_ast = make::trait_( false, - "NewTrait".to_string(), - HasGenericParams::generic_param_list(&impl_ast), - HasGenericParams::where_clause(&impl_ast), + "NewTrait", + impl_ast.generic_param_list(), + impl_ast.where_clause(), trait_items, ); // Change `impl Foo` to `impl NewTrait for Foo` - // First find the PATH_TYPE which is what Foo is. - let impl_name = impl_ast.self_ty().unwrap(); - let trait_name = if let Some(genpars) = impl_ast.generic_param_list() { - format!("NewTrait{}", genpars.to_generic_args()) + let arg_list = if let Some(genpars) = impl_ast.generic_param_list() { + genpars.to_generic_args().to_string() } else { - format!("NewTrait") + "".to_string() }; // // Then replace builder.replace( - impl_name.clone().syntax().text_range(), - format!("{} for {}", trait_name, impl_name.to_string()), + impl_name.syntax().text_range(), + format!("NewTrait{} for {}", arg_list, impl_name.to_string()), ); - builder.replace( - impl_ast.assoc_item_list().unwrap().syntax().text_range(), - impl_items.to_string(), - ); + builder.replace(assoc_items.syntax().text_range(), impl_items.to_string()); // Insert trait before TraitImpl - builder.insert_snippet( - SnippetCap::new(true).unwrap(), + builder.insert( impl_ast.syntax().text_range().start(), - format!("{}\n\n", trait_ast.to_string()), + format!( + "{}\n\n{}", + trait_ast.to_string(), + IndentLevel::from_node(impl_ast.syntax()) + ), ); }, ); @@ -144,17 +154,17 @@ fn remove_items_visibility(item: &ast::AssocItem) { match item { ast::AssocItem::Const(c) => { if let Some(vis) = c.visibility() { - syntax::ted::remove(vis.syntax()); + ted::remove(vis.syntax()); } } ast::AssocItem::Fn(f) => { if let Some(vis) = f.visibility() { - syntax::ted::remove(vis.syntax()); + ted::remove(vis.syntax()); } } ast::AssocItem::TypeAlias(t) => { if let Some(vis) = t.visibility() { - syntax::ted::remove(vis.syntax()); + ted::remove(vis.syntax()); } } _ => (), @@ -168,12 +178,12 @@ fn strip_body(item: &ast::AssocItem) { // In constrast to function bodies, we want to see no ws before a semicolon. // So let's remove them if we see any. if let Some(prev) = body.syntax().prev_sibling_or_token() { - if prev.kind() == syntax::SyntaxKind::WHITESPACE { - syntax::ted::remove(prev); + if prev.kind() == SyntaxKind::WHITESPACE { + ted::remove(prev); } } - syntax::ted::replace(body.syntax(), ast::make::tokens::semicolon()); + ted::replace(body.syntax(), ast::make::tokens::semicolon()); } } _ => (), @@ -185,6 +195,21 @@ mod tests { use super::*; use crate::tests::{check_assist, check_assist_not_applicable}; + #[test] + fn test_trigger_when_cursor_on_header() { + check_assist_not_applicable( + generate_trait_from_impl, + r#" +struct Foo(f64); + +impl Foo { $0 + fn add(&mut self, x: f64) { + self.0 += x; + } +}"#, + ); + } + #[test] fn test_assoc_item_fn() { check_assist( @@ -299,7 +324,7 @@ impl NewTrait for Foo { } #[test] - fn test_e0449_avoided() { + fn test_trait_items_should_not_have_vis() { check_assist( generate_trait_from_impl, r#" @@ -334,4 +359,27 @@ impl Emp$0tyImpl{} "#, ) } + + #[test] + fn test_not_top_level_impl() { + check_assist( + generate_trait_from_impl, + r#" +mod a { + impl S$0 { + fn foo() {} + } +}"#, + r#" +mod a { + trait NewTrait { + fn foo(); + } + + impl NewTrait for S { + fn foo() {} + } +}"#, + ) + } } diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 1675d1af1dd..3facd90a11d 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -865,7 +865,7 @@ pub fn param_list( pub fn trait_( is_unsafe: bool, - ident: String, + ident: &str, gen_params: Option, where_clause: Option, assoc_items: ast::AssocItemList, From 30cbba20de13ed37250d0610980636e43f673022 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 5 Jul 2023 13:32:12 +0200 Subject: [PATCH 4/5] Add snippet insert if client supports it --- .../src/handlers/generate_trait_from_impl.rs | 94 ++++++++++++++----- crates/ide-assists/src/tests/generated.rs | 4 +- 2 files changed, 72 insertions(+), 26 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs index d3192ae4091..2027309940a 100644 --- a/crates/ide-assists/src/handlers/generate_trait_from_impl.rs +++ b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -44,7 +44,7 @@ use syntax::{ // }; // } // -// trait NewTrait { +// trait ${0:TraitName} { // // Used as an associated constant. // const CONST_ASSOC: usize = N * 4; // @@ -53,7 +53,7 @@ use syntax::{ // const_maker! {i32, 7} // } // -// impl NewTrait for Foo { +// impl ${0:TraitName} for Foo { // // Used as an associated constant. // const CONST_ASSOC: usize = N * 4; // @@ -126,23 +126,41 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ "".to_string() }; - // // Then replace - builder.replace( - impl_name.syntax().text_range(), - format!("NewTrait{} for {}", arg_list, impl_name.to_string()), - ); + if let Some(snippet_cap) = ctx.config.snippet_cap { + builder.replace_snippet( + snippet_cap, + impl_name.syntax().text_range(), + format!("${{0:TraitName}}{} for {}", arg_list, impl_name.to_string()), + ); + + // Insert trait before TraitImpl + builder.insert_snippet( + snippet_cap, + impl_ast.syntax().text_range().start(), + format!( + "{}\n\n{}", + trait_ast.to_string().replace("NewTrait", "${0:TraitName}"), + IndentLevel::from_node(impl_ast.syntax()) + ), + ); + } else { + builder.replace( + impl_name.syntax().text_range(), + format!("NewTrait{} for {}", arg_list, impl_name.to_string()), + ); + + // Insert trait before TraitImpl + builder.insert( + impl_ast.syntax().text_range().start(), + format!( + "{}\n\n{}", + trait_ast.to_string(), + IndentLevel::from_node(impl_ast.syntax()) + ), + ); + } builder.replace(assoc_items.syntax().text_range(), impl_items.to_string()); - - // Insert trait before TraitImpl - builder.insert( - impl_ast.syntax().text_range().start(), - format!( - "{}\n\n{}", - trait_ast.to_string(), - IndentLevel::from_node(impl_ast.syntax()) - ), - ); }, ); @@ -193,7 +211,7 @@ fn strip_body(item: &ast::AssocItem) { #[cfg(test)] mod tests { use super::*; - use crate::tests::{check_assist, check_assist_not_applicable}; + use crate::tests::{check_assist, check_assist_no_snippet_cap, check_assist_not_applicable}; #[test] fn test_trigger_when_cursor_on_header() { @@ -212,7 +230,7 @@ impl Foo { $0 #[test] fn test_assoc_item_fn() { - check_assist( + check_assist_no_snippet_cap( generate_trait_from_impl, r#" struct Foo(f64); @@ -239,7 +257,7 @@ impl NewTrait for Foo { #[test] fn test_assoc_item_macro() { - check_assist( + check_assist_no_snippet_cap( generate_trait_from_impl, r#" struct Foo; @@ -274,7 +292,7 @@ impl NewTrait for Foo { #[test] fn test_assoc_item_const() { - check_assist( + check_assist_no_snippet_cap( generate_trait_from_impl, r#" struct Foo; @@ -297,7 +315,7 @@ impl NewTrait for Foo { #[test] fn test_impl_with_generics() { - check_assist( + check_assist_no_snippet_cap( generate_trait_from_impl, r#" struct Foo([i32; N]); @@ -325,7 +343,7 @@ impl NewTrait for Foo { #[test] fn test_trait_items_should_not_have_vis() { - check_assist( + check_assist_no_snippet_cap( generate_trait_from_impl, r#" struct Foo; @@ -362,7 +380,7 @@ impl Emp$0tyImpl{} #[test] fn test_not_top_level_impl() { - check_assist( + check_assist_no_snippet_cap( generate_trait_from_impl, r#" mod a { @@ -382,4 +400,32 @@ mod a { }"#, ) } + + #[test] + fn test_snippet_cap_is_some() { + check_assist( + generate_trait_from_impl, + r#" +struct Foo([i32; N]); + +impl F$0oo { + // Used as an associated constant. + const CONST: usize = N * 4; +} + "#, + r#" +struct Foo([i32; N]); + +trait ${0:TraitName} { + // Used as an associated constant. + const CONST: usize = N * 4; +} + +impl ${0:TraitName} for Foo { + // Used as an associated constant. + const CONST: usize = N * 4; +} + "#, + ) + } } diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 2404fecc452..edab13c754d 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1533,7 +1533,7 @@ macro_rules! const_maker { }; } -trait NewTrait { +trait ${0:TraitName} { // Used as an associated constant. const CONST_ASSOC: usize = N * 4; @@ -1542,7 +1542,7 @@ trait NewTrait { const_maker! {i32, 7} } -impl NewTrait for Foo { +impl ${0:TraitName} for Foo { // Used as an associated constant. const CONST_ASSOC: usize = N * 4; From 8edb3e192aa9b7faff864e852b8b3302fcd426b1 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Wed, 5 Jul 2023 16:45:54 +0200 Subject: [PATCH 5/5] Minor changes --- crates/ide-assists/src/handlers/generate_trait_from_impl.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs index 2027309940a..0f67380d12b 100644 --- a/crates/ide-assists/src/handlers/generate_trait_from_impl.rs +++ b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -105,8 +105,6 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ remove_items_visibility(&item); }); - ted::replace(assoc_items.clone_for_update().syntax(), impl_items.syntax()); - impl_items.assoc_items().for_each(|item| { remove_items_visibility(&item); }); @@ -201,7 +199,7 @@ fn strip_body(item: &ast::AssocItem) { } } - ted::replace(body.syntax(), ast::make::tokens::semicolon()); + ted::replace(body.syntax(), make::tokens::semicolon()); } } _ => (),