Skip to content

Commit 4fc3cff

Browse files
authored
Merge pull request #22091 from Shourya742/2026-04-19-remove-generate-impl-text
Remove generate impl text from utils
2 parents 848e6aa + 242deb5 commit 4fc3cff

3 files changed

Lines changed: 119 additions & 170 deletions

File tree

crates/ide-assists/src/handlers/generate_enum_is_method.rs

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use ide_db::assists::GroupLabel;
2-
use itertools::Itertools;
32
use stdx::to_lower_snake_case;
4-
use syntax::ast::HasVisibility;
5-
use syntax::ast::{self, AstNode, HasName};
3+
use syntax::{
4+
AstNode, Edition,
5+
ast::{self, HasName, HasVisibility, edit::AstNodeEdit},
6+
syntax_editor::Position,
7+
};
68

79
use crate::{
810
AssistContext, AssistId, Assists,
9-
utils::{add_method_to_adt, find_struct_impl, is_selected},
11+
utils::{find_struct_impl, generate_impl_with_item, is_selected},
1012
};
1113

1214
// Assist: generate_enum_is_method
@@ -64,27 +66,63 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext<'_>
6466
target,
6567
|builder| {
6668
let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} "));
67-
let method = methods
69+
70+
let fn_items: Vec<ast::AssocItem> = methods
6871
.iter()
69-
.map(|Method { pattern_suffix, fn_name, variant_name }| {
70-
format!(
71-
" \
72-
/// Returns `true` if the {enum_lowercase_name} is [`{variant_name}`].
73-
///
74-
/// [`{variant_name}`]: {enum_name}::{variant_name}
75-
#[must_use]
76-
{vis}fn {fn_name}(&self) -> bool {{
77-
matches!(self, Self::{variant_name}{pattern_suffix})
78-
}}",
79-
)
80-
})
81-
.join("\n\n");
82-
83-
add_method_to_adt(builder, &parent_enum, impl_def, &method);
72+
.map(|method| build_fn_item(method, &enum_lowercase_name, &enum_name, &vis))
73+
.collect();
74+
75+
if let Some(impl_def) = &impl_def {
76+
let editor = builder.make_editor(impl_def.syntax());
77+
impl_def.assoc_item_list().unwrap().add_items(&editor, fn_items);
78+
builder.add_file_edits(ctx.vfs_file_id(), editor);
79+
return;
80+
}
81+
82+
let editor = builder.make_editor(parent_enum.syntax());
83+
let make = editor.make();
84+
let indent = parent_enum.indent_level();
85+
let assoc_list = make.assoc_item_list(fn_items);
86+
let new_impl = generate_impl_with_item(make, &parent_enum, Some(assoc_list));
87+
editor.insert_all(
88+
Position::after(parent_enum.syntax()),
89+
vec![
90+
make.whitespace(&format!("\n\n{indent}")).into(),
91+
new_impl.syntax().clone().into(),
92+
],
93+
);
94+
builder.add_file_edits(ctx.vfs_file_id(), editor);
8495
},
8596
)
8697
}
8798

99+
fn build_fn_item(
100+
method: &Method,
101+
enum_lowercase_name: &str,
102+
enum_name: &ast::Name,
103+
vis: &str,
104+
) -> ast::AssocItem {
105+
let Method { pattern_suffix, fn_name, variant_name } = method;
106+
let fn_text = format!(
107+
"/// Returns `true` if the {enum_lowercase_name} is [`{variant_name}`].
108+
///
109+
/// [`{variant_name}`]: {enum_name}::{variant_name}
110+
#[must_use]
111+
{vis}fn {fn_name}(&self) -> bool {{
112+
matches!(self, Self::{variant_name}{pattern_suffix})
113+
}}"
114+
);
115+
let wrapped = format!("impl X {{ {fn_text} }}");
116+
let parse = syntax::SourceFile::parse(&wrapped, Edition::CURRENT);
117+
let fn_ = parse
118+
.tree()
119+
.syntax()
120+
.descendants()
121+
.find_map(ast::Fn::cast)
122+
.expect("fn text must produce a valid fn node");
123+
ast::AssocItem::Fn(fn_.indent(1.into()))
124+
}
125+
88126
struct Method {
89127
pattern_suffix: &'static str,
90128
fn_name: String,

crates/ide-assists/src/handlers/generate_enum_projection_method.rs

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use ide_db::assists::GroupLabel;
2-
use itertools::Itertools;
32
use stdx::to_lower_snake_case;
4-
use syntax::ast::HasVisibility;
5-
use syntax::ast::{self, AstNode, HasName};
3+
use syntax::{
4+
AstNode, Edition,
5+
ast::{self, HasName, HasVisibility, edit::AstNodeEdit},
6+
syntax_editor::Position,
7+
};
68

79
use crate::{
810
AssistContext, AssistId, Assists,
9-
utils::{add_method_to_adt, find_struct_impl, is_selected},
11+
utils::{find_struct_impl, generate_impl_with_item, is_selected},
1012
};
1113

1214
// Assist: generate_enum_try_into_method
@@ -116,15 +118,6 @@ fn generate_enum_projection_method(
116118
assist_description: &str,
117119
props: ProjectionProps,
118120
) -> Option<()> {
119-
let ProjectionProps {
120-
fn_name_prefix,
121-
self_param,
122-
return_prefix,
123-
return_suffix,
124-
happy_case,
125-
sad_case,
126-
} = props;
127-
128121
let variant = ctx.find_node_at_offset::<ast::Variant>()?;
129122
let parent_enum = ast::Adt::Enum(variant.parent_enum());
130123
let variants = variant
@@ -135,7 +128,7 @@ fn generate_enum_projection_method(
135128
.collect::<Vec<_>>();
136129
let methods = variants
137130
.iter()
138-
.map(|variant| Method::new(variant, fn_name_prefix))
131+
.map(|variant| Method::new(variant, props.fn_name_prefix))
139132
.collect::<Option<Vec<_>>>()?;
140133
let fn_names = methods.iter().map(|it| it.fn_name.clone()).collect::<Vec<_>>();
141134
stdx::never!(variants.is_empty());
@@ -151,30 +144,66 @@ fn generate_enum_projection_method(
151144
target,
152145
|builder| {
153146
let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} "));
147+
let must_use = if ctx.config.assist_emit_must_use { "#[must_use]\n" } else { "" };
154148

155-
let must_use = if ctx.config.assist_emit_must_use { "#[must_use]\n " } else { "" };
156-
157-
let method = methods
149+
let fn_items: Vec<ast::AssocItem> = methods
158150
.iter()
159-
.map(|Method { pattern_suffix, field_type, bound_name, fn_name, variant_name }| {
160-
format!(
161-
" \
162-
{must_use}{vis}fn {fn_name}({self_param}) -> {return_prefix}{field_type}{return_suffix} {{
163-
if let Self::{variant_name}{pattern_suffix} = self {{
164-
{happy_case}({bound_name})
165-
}} else {{
166-
{sad_case}
167-
}}
168-
}}"
169-
)
170-
})
171-
.join("\n\n");
151+
.map(|method| build_fn_item(method, &vis, must_use, &props))
152+
.collect();
153+
154+
if let Some(impl_def) = &impl_def {
155+
let editor = builder.make_editor(impl_def.syntax());
156+
impl_def.assoc_item_list().unwrap().add_items(&editor, fn_items);
157+
builder.add_file_edits(ctx.vfs_file_id(), editor);
158+
return;
159+
}
172160

173-
add_method_to_adt(builder, &parent_enum, impl_def, &method);
161+
let editor = builder.make_editor(parent_enum.syntax());
162+
let make = editor.make();
163+
let indent = parent_enum.indent_level();
164+
let assoc_list = make.assoc_item_list(fn_items);
165+
let new_impl = generate_impl_with_item(make, &parent_enum, Some(assoc_list));
166+
editor.insert_all(
167+
Position::after(parent_enum.syntax()),
168+
vec![
169+
make.whitespace(&format!("\n\n{indent}")).into(),
170+
new_impl.syntax().clone().into(),
171+
],
172+
);
173+
builder.add_file_edits(ctx.vfs_file_id(), editor);
174174
},
175175
)
176176
}
177177

178+
fn build_fn_item(
179+
method: &Method,
180+
vis: &str,
181+
must_use: &str,
182+
props: &ProjectionProps,
183+
) -> ast::AssocItem {
184+
let Method { pattern_suffix, field_type, bound_name, fn_name, variant_name } = method;
185+
let ProjectionProps { self_param, return_prefix, return_suffix, happy_case, sad_case, .. } =
186+
props;
187+
let fn_text = format!(
188+
"{must_use}{vis}fn {fn_name}({self_param}) -> {return_prefix}{field_type}{return_suffix} {{
189+
if let Self::{variant_name}{pattern_suffix} = self {{
190+
{happy_case}({bound_name})
191+
}} else {{
192+
{sad_case}
193+
}}
194+
}}"
195+
);
196+
let wrapped = format!("impl X {{ {fn_text} }}");
197+
let parse = syntax::SourceFile::parse(&wrapped, Edition::CURRENT);
198+
let fn_ = parse
199+
.tree()
200+
.syntax()
201+
.descendants()
202+
.find_map(ast::Fn::cast)
203+
.expect("fn text must produce a valid fn node");
204+
ast::AssocItem::Fn(fn_.indent(1.into()))
205+
}
206+
178207
struct Method {
179208
pattern_suffix: String,
180209
field_type: ast::Type,
@@ -185,6 +214,7 @@ struct Method {
185214

186215
impl Method {
187216
fn new(variant: &ast::Variant, fn_name_prefix: &str) -> Option<Self> {
217+
use itertools::Itertools as _;
188218
let variant_name = variant.name()?;
189219
let fn_name = format!("{fn_name_prefix}_{}", &to_lower_snake_case(&variant_name.text()));
190220

crates/ide-assists/src/utils.rs

Lines changed: 0 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use ide_db::{
1515
syntax_helpers::{node_ext::preorder_expr, prettify_macro_expansion},
1616
};
1717
use itertools::Itertools;
18-
use stdx::format_to;
1918
use syntax::{
2019
AstNode, AstToken, Direction, NodeOrToken, SourceFile,
2120
SyntaxKind::*,
@@ -530,102 +529,6 @@ fn has_any_fn(imp: &ast::Impl, names: &[String]) -> bool {
530529
false
531530
}
532531

533-
/// Find the end of the `impl` block for the given `ast::Impl`.
534-
//
535-
// FIXME: this partially overlaps with `find_struct_impl`
536-
pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Option<TextSize> {
537-
buf.push('\n');
538-
let end = impl_def
539-
.assoc_item_list()
540-
.and_then(|it| it.r_curly_token())?
541-
.prev_sibling_or_token()?
542-
.text_range()
543-
.end();
544-
Some(end)
545-
}
546-
547-
/// Generates the surrounding `impl Type { <code> }` including type and lifetime
548-
/// parameters.
549-
// FIXME: migrate remaining uses to `generate_impl`
550-
pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String {
551-
generate_impl_text_inner(adt, None, true, code)
552-
}
553-
554-
fn generate_impl_text_inner(
555-
adt: &ast::Adt,
556-
trait_text: Option<&str>,
557-
trait_is_transitive: bool,
558-
code: &str,
559-
) -> String {
560-
// Ensure lifetime params are before type & const params
561-
let generic_params = adt.generic_param_list().map(|generic_params| {
562-
let lifetime_params =
563-
generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam);
564-
let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| {
565-
let param = match param {
566-
ast::TypeOrConstParam::Type(param) => {
567-
// remove defaults since they can't be specified in impls
568-
let mut bounds =
569-
param.type_bound_list().map_or_else(Vec::new, |it| it.bounds().collect());
570-
if let Some(trait_) = trait_text {
571-
// Add the current trait to `bounds` if the trait is transitive,
572-
// meaning `impl<T> Trait for U<T>` requires `T: Trait`.
573-
if trait_is_transitive {
574-
bounds.push(make::type_bound_text(trait_));
575-
}
576-
};
577-
// `{ty_param}: {bounds}`
578-
let param = make::type_param(param.name()?, make::type_bound_list(bounds));
579-
ast::GenericParam::TypeParam(param)
580-
}
581-
ast::TypeOrConstParam::Const(param) => {
582-
// remove defaults since they can't be specified in impls
583-
let param = make::const_param(param.name()?, param.ty()?);
584-
ast::GenericParam::ConstParam(param)
585-
}
586-
};
587-
Some(param)
588-
});
589-
590-
make::generic_param_list(itertools::chain(lifetime_params, ty_or_const_params))
591-
});
592-
593-
// FIXME: use syntax::make & mutable AST apis instead
594-
// `trait_text` and `code` can't be opaque blobs of text
595-
let mut buf = String::with_capacity(code.len());
596-
597-
// Copy any cfg attrs from the original adt
598-
buf.push_str("\n\n");
599-
let cfg_attrs = adt.attrs().filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_))));
600-
cfg_attrs.for_each(|attr| buf.push_str(&format!("{attr}\n")));
601-
602-
// `impl{generic_params} {trait_text} for {name}{generic_params.to_generic_args()}`
603-
buf.push_str("impl");
604-
if let Some(generic_params) = &generic_params {
605-
format_to!(buf, "{generic_params}");
606-
}
607-
buf.push(' ');
608-
if let Some(trait_text) = trait_text {
609-
buf.push_str(trait_text);
610-
buf.push_str(" for ");
611-
}
612-
buf.push_str(&adt.name().unwrap().text());
613-
if let Some(generic_params) = generic_params {
614-
format_to!(buf, "{}", generic_params.to_generic_args());
615-
}
616-
617-
match adt.where_clause() {
618-
Some(where_clause) => {
619-
format_to!(buf, "\n{where_clause}\n{{\n{code}\n}}");
620-
}
621-
None => {
622-
format_to!(buf, " {{\n{code}\n}}");
623-
}
624-
}
625-
626-
buf
627-
}
628-
629532
/// Generates the corresponding `impl Type {}` including type and lifetime
630533
/// parameters.
631534
pub(crate) fn generate_impl_with_item(
@@ -917,28 +820,6 @@ fn generic_param_associated_bounds_with_factory(
917820
trait_where_clause.peek().is_some().then(|| make.where_clause(trait_where_clause))
918821
}
919822

920-
pub(crate) fn add_method_to_adt(
921-
builder: &mut SourceChangeBuilder,
922-
adt: &ast::Adt,
923-
impl_def: Option<ast::Impl>,
924-
method: &str,
925-
) {
926-
let mut buf = String::with_capacity(method.len() + 2);
927-
if impl_def.is_some() {
928-
buf.push('\n');
929-
}
930-
buf.push_str(method);
931-
932-
let start_offset = impl_def
933-
.and_then(|impl_def| find_impl_block_end(impl_def, &mut buf))
934-
.unwrap_or_else(|| {
935-
buf = generate_impl_text(adt, &buf);
936-
adt.syntax().text_range().end()
937-
});
938-
939-
builder.insert(start_offset, buf);
940-
}
941-
942823
#[derive(Debug)]
943824
pub(crate) struct ReferenceConversion<'db> {
944825
conversion: ReferenceConversionType,

0 commit comments

Comments
 (0)