Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #22091 from Shourya742/2026-04-19-remove-generate-impl-text
Remove generate impl text from utils
| -rw-r--r-- | crates/ide-assists/src/handlers/generate_enum_is_method.rs | 78 | ||||
| -rw-r--r-- | crates/ide-assists/src/handlers/generate_enum_projection_method.rs | 92 | ||||
| -rw-r--r-- | crates/ide-assists/src/utils.rs | 119 |
3 files changed, 119 insertions, 170 deletions
diff --git a/crates/ide-assists/src/handlers/generate_enum_is_method.rs b/crates/ide-assists/src/handlers/generate_enum_is_method.rs index b866022a7d..e2783811f7 100644 --- a/crates/ide-assists/src/handlers/generate_enum_is_method.rs +++ b/crates/ide-assists/src/handlers/generate_enum_is_method.rs @@ -1,12 +1,14 @@ use ide_db::assists::GroupLabel; -use itertools::Itertools; use stdx::to_lower_snake_case; -use syntax::ast::HasVisibility; -use syntax::ast::{self, AstNode, HasName}; +use syntax::{ + AstNode, Edition, + ast::{self, HasName, HasVisibility, edit::AstNodeEdit}, + syntax_editor::Position, +}; use crate::{ AssistContext, AssistId, Assists, - utils::{add_method_to_adt, find_struct_impl, is_selected}, + utils::{find_struct_impl, generate_impl_with_item, is_selected}, }; // Assist: generate_enum_is_method @@ -64,27 +66,63 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext<'_> target, |builder| { let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} ")); - let method = methods + + let fn_items: Vec<ast::AssocItem> = methods .iter() - .map(|Method { pattern_suffix, fn_name, variant_name }| { - format!( - " \ - /// Returns `true` if the {enum_lowercase_name} is [`{variant_name}`]. - /// - /// [`{variant_name}`]: {enum_name}::{variant_name} - #[must_use] - {vis}fn {fn_name}(&self) -> bool {{ - matches!(self, Self::{variant_name}{pattern_suffix}) - }}", - ) - }) - .join("\n\n"); - - add_method_to_adt(builder, &parent_enum, impl_def, &method); + .map(|method| build_fn_item(method, &enum_lowercase_name, &enum_name, &vis)) + .collect(); + + if let Some(impl_def) = &impl_def { + let editor = builder.make_editor(impl_def.syntax()); + impl_def.assoc_item_list().unwrap().add_items(&editor, fn_items); + builder.add_file_edits(ctx.vfs_file_id(), editor); + return; + } + + let editor = builder.make_editor(parent_enum.syntax()); + let make = editor.make(); + let indent = parent_enum.indent_level(); + let assoc_list = make.assoc_item_list(fn_items); + let new_impl = generate_impl_with_item(make, &parent_enum, Some(assoc_list)); + editor.insert_all( + Position::after(parent_enum.syntax()), + vec![ + make.whitespace(&format!("\n\n{indent}")).into(), + new_impl.syntax().clone().into(), + ], + ); + builder.add_file_edits(ctx.vfs_file_id(), editor); }, ) } +fn build_fn_item( + method: &Method, + enum_lowercase_name: &str, + enum_name: &ast::Name, + vis: &str, +) -> ast::AssocItem { + let Method { pattern_suffix, fn_name, variant_name } = method; + let fn_text = format!( + "/// Returns `true` if the {enum_lowercase_name} is [`{variant_name}`]. +/// +/// [`{variant_name}`]: {enum_name}::{variant_name} +#[must_use] +{vis}fn {fn_name}(&self) -> bool {{ + matches!(self, Self::{variant_name}{pattern_suffix}) +}}" + ); + let wrapped = format!("impl X {{ {fn_text} }}"); + let parse = syntax::SourceFile::parse(&wrapped, Edition::CURRENT); + let fn_ = parse + .tree() + .syntax() + .descendants() + .find_map(ast::Fn::cast) + .expect("fn text must produce a valid fn node"); + ast::AssocItem::Fn(fn_.indent(1.into())) +} + struct Method { pattern_suffix: &'static str, fn_name: String, diff --git a/crates/ide-assists/src/handlers/generate_enum_projection_method.rs b/crates/ide-assists/src/handlers/generate_enum_projection_method.rs index 39a6382b7c..9a97ad1e8f 100644 --- a/crates/ide-assists/src/handlers/generate_enum_projection_method.rs +++ b/crates/ide-assists/src/handlers/generate_enum_projection_method.rs @@ -1,12 +1,14 @@ use ide_db::assists::GroupLabel; -use itertools::Itertools; use stdx::to_lower_snake_case; -use syntax::ast::HasVisibility; -use syntax::ast::{self, AstNode, HasName}; +use syntax::{ + AstNode, Edition, + ast::{self, HasName, HasVisibility, edit::AstNodeEdit}, + syntax_editor::Position, +}; use crate::{ AssistContext, AssistId, Assists, - utils::{add_method_to_adt, find_struct_impl, is_selected}, + utils::{find_struct_impl, generate_impl_with_item, is_selected}, }; // Assist: generate_enum_try_into_method @@ -116,15 +118,6 @@ fn generate_enum_projection_method( assist_description: &str, props: ProjectionProps, ) -> Option<()> { - let ProjectionProps { - fn_name_prefix, - self_param, - return_prefix, - return_suffix, - happy_case, - sad_case, - } = props; - let variant = ctx.find_node_at_offset::<ast::Variant>()?; let parent_enum = ast::Adt::Enum(variant.parent_enum()); let variants = variant @@ -135,7 +128,7 @@ fn generate_enum_projection_method( .collect::<Vec<_>>(); let methods = variants .iter() - .map(|variant| Method::new(variant, fn_name_prefix)) + .map(|variant| Method::new(variant, props.fn_name_prefix)) .collect::<Option<Vec<_>>>()?; let fn_names = methods.iter().map(|it| it.fn_name.clone()).collect::<Vec<_>>(); stdx::never!(variants.is_empty()); @@ -151,30 +144,66 @@ fn generate_enum_projection_method( target, |builder| { let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} ")); + let must_use = if ctx.config.assist_emit_must_use { "#[must_use]\n" } else { "" }; - let must_use = if ctx.config.assist_emit_must_use { "#[must_use]\n " } else { "" }; - - let method = methods + let fn_items: Vec<ast::AssocItem> = methods .iter() - .map(|Method { pattern_suffix, field_type, bound_name, fn_name, variant_name }| { - format!( - " \ - {must_use}{vis}fn {fn_name}({self_param}) -> {return_prefix}{field_type}{return_suffix} {{ - if let Self::{variant_name}{pattern_suffix} = self {{ - {happy_case}({bound_name}) - }} else {{ - {sad_case} - }} - }}" - ) - }) - .join("\n\n"); + .map(|method| build_fn_item(method, &vis, must_use, &props)) + .collect(); + + if let Some(impl_def) = &impl_def { + let editor = builder.make_editor(impl_def.syntax()); + impl_def.assoc_item_list().unwrap().add_items(&editor, fn_items); + builder.add_file_edits(ctx.vfs_file_id(), editor); + return; + } - add_method_to_adt(builder, &parent_enum, impl_def, &method); + let editor = builder.make_editor(parent_enum.syntax()); + let make = editor.make(); + let indent = parent_enum.indent_level(); + let assoc_list = make.assoc_item_list(fn_items); + let new_impl = generate_impl_with_item(make, &parent_enum, Some(assoc_list)); + editor.insert_all( + Position::after(parent_enum.syntax()), + vec![ + make.whitespace(&format!("\n\n{indent}")).into(), + new_impl.syntax().clone().into(), + ], + ); + builder.add_file_edits(ctx.vfs_file_id(), editor); }, ) } +fn build_fn_item( + method: &Method, + vis: &str, + must_use: &str, + props: &ProjectionProps, +) -> ast::AssocItem { + let Method { pattern_suffix, field_type, bound_name, fn_name, variant_name } = method; + let ProjectionProps { self_param, return_prefix, return_suffix, happy_case, sad_case, .. } = + props; + let fn_text = format!( + "{must_use}{vis}fn {fn_name}({self_param}) -> {return_prefix}{field_type}{return_suffix} {{ + if let Self::{variant_name}{pattern_suffix} = self {{ + {happy_case}({bound_name}) + }} else {{ + {sad_case} + }} +}}" + ); + let wrapped = format!("impl X {{ {fn_text} }}"); + let parse = syntax::SourceFile::parse(&wrapped, Edition::CURRENT); + let fn_ = parse + .tree() + .syntax() + .descendants() + .find_map(ast::Fn::cast) + .expect("fn text must produce a valid fn node"); + ast::AssocItem::Fn(fn_.indent(1.into())) +} + struct Method { pattern_suffix: String, field_type: ast::Type, @@ -185,6 +214,7 @@ struct Method { impl Method { fn new(variant: &ast::Variant, fn_name_prefix: &str) -> Option<Self> { + use itertools::Itertools as _; let variant_name = variant.name()?; let fn_name = format!("{fn_name_prefix}_{}", &to_lower_snake_case(&variant_name.text())); diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index d316162b27..bf1062d207 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -15,7 +15,6 @@ use ide_db::{ syntax_helpers::{node_ext::preorder_expr, prettify_macro_expansion}, }; use itertools::Itertools; -use stdx::format_to; use syntax::{ AstNode, AstToken, Direction, NodeOrToken, SourceFile, SyntaxKind::*, @@ -530,102 +529,6 @@ fn has_any_fn(imp: &ast::Impl, names: &[String]) -> bool { false } -/// Find the end of the `impl` block for the given `ast::Impl`. -// -// FIXME: this partially overlaps with `find_struct_impl` -pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Option<TextSize> { - buf.push('\n'); - let end = impl_def - .assoc_item_list() - .and_then(|it| it.r_curly_token())? - .prev_sibling_or_token()? - .text_range() - .end(); - Some(end) -} - -/// Generates the surrounding `impl Type { <code> }` including type and lifetime -/// parameters. -// FIXME: migrate remaining uses to `generate_impl` -pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String { - generate_impl_text_inner(adt, None, true, code) -} - -fn generate_impl_text_inner( - adt: &ast::Adt, - trait_text: Option<&str>, - trait_is_transitive: bool, - code: &str, -) -> String { - // Ensure lifetime params are before type & const params - let generic_params = adt.generic_param_list().map(|generic_params| { - let lifetime_params = - generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam); - let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| { - let param = match param { - ast::TypeOrConstParam::Type(param) => { - // remove defaults since they can't be specified in impls - let mut bounds = - param.type_bound_list().map_or_else(Vec::new, |it| it.bounds().collect()); - if let Some(trait_) = trait_text { - // Add the current trait to `bounds` if the trait is transitive, - // meaning `impl<T> Trait for U<T>` requires `T: Trait`. - if trait_is_transitive { - bounds.push(make::type_bound_text(trait_)); - } - }; - // `{ty_param}: {bounds}` - let param = make::type_param(param.name()?, make::type_bound_list(bounds)); - ast::GenericParam::TypeParam(param) - } - ast::TypeOrConstParam::Const(param) => { - // remove defaults since they can't be specified in impls - let param = make::const_param(param.name()?, param.ty()?); - ast::GenericParam::ConstParam(param) - } - }; - Some(param) - }); - - make::generic_param_list(itertools::chain(lifetime_params, ty_or_const_params)) - }); - - // FIXME: use syntax::make & mutable AST apis instead - // `trait_text` and `code` can't be opaque blobs of text - let mut buf = String::with_capacity(code.len()); - - // Copy any cfg attrs from the original adt - buf.push_str("\n\n"); - let cfg_attrs = adt.attrs().filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))); - cfg_attrs.for_each(|attr| buf.push_str(&format!("{attr}\n"))); - - // `impl{generic_params} {trait_text} for {name}{generic_params.to_generic_args()}` - buf.push_str("impl"); - if let Some(generic_params) = &generic_params { - format_to!(buf, "{generic_params}"); - } - buf.push(' '); - if let Some(trait_text) = trait_text { - buf.push_str(trait_text); - buf.push_str(" for "); - } - buf.push_str(&adt.name().unwrap().text()); - if let Some(generic_params) = generic_params { - format_to!(buf, "{}", generic_params.to_generic_args()); - } - - match adt.where_clause() { - Some(where_clause) => { - format_to!(buf, "\n{where_clause}\n{{\n{code}\n}}"); - } - None => { - format_to!(buf, " {{\n{code}\n}}"); - } - } - - buf -} - /// Generates the corresponding `impl Type {}` including type and lifetime /// parameters. pub(crate) fn generate_impl_with_item( @@ -917,28 +820,6 @@ fn generic_param_associated_bounds_with_factory( trait_where_clause.peek().is_some().then(|| make.where_clause(trait_where_clause)) } -pub(crate) fn add_method_to_adt( - builder: &mut SourceChangeBuilder, - adt: &ast::Adt, - impl_def: Option<ast::Impl>, - method: &str, -) { - let mut buf = String::with_capacity(method.len() + 2); - if impl_def.is_some() { - buf.push('\n'); - } - buf.push_str(method); - - let start_offset = impl_def - .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) - .unwrap_or_else(|| { - buf = generate_impl_text(adt, &buf); - adt.syntax().text_range().end() - }); - - builder.insert(start_offset, buf); -} - #[derive(Debug)] pub(crate) struct ReferenceConversion<'db> { conversion: ReferenceConversionType, |