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
Shoyu Vanilla (Flint) 5 weeks ago
parent 848e6aa · parent 242deb5 · commit 4fc3cff
-rw-r--r--crates/ide-assists/src/handlers/generate_enum_is_method.rs78
-rw-r--r--crates/ide-assists/src/handlers/generate_enum_projection_method.rs92
-rw-r--r--crates/ide-assists/src/utils.rs119
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,