Skip to content

Commit 7676177

Browse files
committed
feat: extract struct from function signature
moving away from ted
1 parent 01a3627 commit 7676177

File tree

1 file changed

+75
-80
lines changed

1 file changed

+75
-80
lines changed

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

Lines changed: 75 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,30 @@ use std::ops::Range;
22

33
use hir::{HasCrate, Module, ModuleDef};
44
use ide_db::{
5-
FxHashSet, RootDatabase,
5+
FileId,
6+
FxHashSet,
7+
RootDatabase,
68
assists::AssistId,
79
defs::Definition,
810
helpers::mod_path_to_ast,
11+
// this relies on ted
912
imports::insert_use::{ImportScope, InsertUseConfig, insert_use},
1013
path_transform::PathTransform,
1114
search::FileReference,
1215
source_change::SourceChangeBuilder,
1316
};
1417
use itertools::Itertools;
1518
use syntax::{
16-
AstNode, Edition, SyntaxElement, SyntaxKind, SyntaxNode, T,
19+
AstNode, Edition, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, T,
1720
algo::find_node_at_range,
1821
ast::{
19-
self, HasArgList, HasAttrs, HasGenericParams, HasName, HasVisibility,
22+
self, HasArgList, HasGenericParams, HasName, HasVisibility,
2023
edit::{AstNodeEdit, IndentLevel},
2124
make,
25+
syntax_factory::SyntaxFactory,
2226
},
2327
match_ast,
24-
ted::{self, Element},
28+
syntax_editor::{Element, Position, SyntaxEditor},
2529
};
2630

2731
use crate::{AssistContext, Assists};
@@ -85,9 +89,11 @@ pub(crate) fn extract_struct_from_function_signature(
8589
"Extract struct from signature of a function",
8690
target,
8791
|builder| {
92+
let make = SyntaxFactory::with_mappings();
93+
let mut editor = builder.make_editor(func.syntax());
8894
let n_new_lifetimes = field_list.fields().filter_map(|f|f.ty()).map(|t|new_life_time_count(&t)).sum();
8995
let edition = fn_hir.krate(ctx.db()).edition(ctx.db());
90-
let enum_module_def = ModuleDef::from(fn_hir);
96+
let function_module_def = ModuleDef::from(fn_hir);
9197

9298
let usages = Definition::Function(fn_hir).usages(&ctx.sema).all();
9399
let mut visited_modules_set = FxHashSet::default();
@@ -102,46 +108,36 @@ pub(crate) fn extract_struct_from_function_signature(
102108
def_file_references = Some(references);
103109
continue;
104110
}
105-
builder.edit_file(file_id.file_id(ctx.db()));
106111
let processed = process_references(
107112
ctx,
108-
builder,
109113
&mut visited_modules_set,
110-
&enum_module_def,
114+
&function_module_def,
111115
references,
112116
name.clone()
113117
);
114118
processed.into_iter().for_each(|(path, import)| {
115-
apply_references(ctx.config.insert_use, path ,import, edition, used_params_range.clone(), &field_list,
116-
name.clone(),
119+
apply_references( builder,ctx.config.insert_use, path ,import, edition, used_params_range.clone(), &field_list,
120+
name.clone(),file_id.file_id(ctx.db()),
117121
);
118122
});
119123
}
120124

121125
tracing::info!("extract_struct_from_function_signature: starting edit");
122-
builder.edit_file(ctx.vfs_file_id());
123-
// atl the make muts should generally before any edits happen
124-
let func_mut = builder.make_mut(func.clone());
125126
// if in impl block then put struct before the impl block
126127
let (indent, syntax) = param_list.self_param().and_then(|_|ctx.find_node_at_range::<ast::Impl>() )
127-
.map(|imp|( imp.indent_level(), builder.make_syntax_mut(imp.syntax().clone()))).unwrap_or((func.indent_level(), func_mut.syntax().clone()));
128-
builder.make_mut(param_list.clone());
129-
let used_param_list = used_param_list.into_iter().map(|p| builder.make_mut(p)).collect_vec();
128+
.map(|imp|( imp.indent_level(), imp.syntax().clone())).unwrap_or((func.indent_level(), func.syntax().clone()));
130129
tracing::info!("extract_struct_from_function_signature: editing main file");
131-
// this has to be after the edit_file (order matters)
132-
// func and param_list must be "mut" for the effect to work on used_param_list
133130
if let Some(references) = def_file_references {
134131
let processed = process_references(
135132
ctx,
136-
builder,
137133
&mut visited_modules_set,
138-
&enum_module_def,
134+
&function_module_def,
139135
references,
140136
name.clone()
141137
);
142138
processed.into_iter().for_each(|(path, import)| {
143-
apply_references(ctx.config.insert_use, path, import, edition, used_params_range.clone(), &field_list,
144-
name.clone(),
139+
apply_references(builder, ctx.config.insert_use, path, import, edition, used_params_range.clone(), &field_list,
140+
name.clone(), ctx.vfs_file_id()
145141
);
146142
});
147143
}
@@ -173,23 +169,25 @@ pub(crate) fn extract_struct_from_function_signature(
173169
}
174170
}
175171
} else {
176-
field_list.clone_for_update()
172+
field_list
177173
};
178-
field_list.fields().filter_map(|f|f.ty()).try_for_each(|t|generate_new_lifetimes(&t, &mut generics));
174+
field_list.fields().filter_map(|f|f.ty()).try_for_each(|t|generate_new_lifetimes(&mut editor, &t, &mut generics));
179175
tracing::info!("extract_struct_from_function_signature: collecting fields");
180-
let def = create_struct_def(name.clone(), &func_mut, &used_param_list, &field_list, generics);
176+
let def = create_struct_def(& make, name.clone(), &func, &used_param_list, &field_list, generics);
181177
tracing::info!("extract_struct_from_function_signature: creating struct");
182178
let def = def.indent(indent);
183-
ted::insert_all(
184-
ted::Position::before(syntax),
179+
editor.insert_all(
180+
Position::before(syntax),
185181
vec![
186182
def.syntax().clone().into(),
187183
make::tokens::whitespace(&format!("\n\n{indent}")).into(),
188184
],
189185
);
190186
tracing::info!("extract_struct_from_function_signature: inserting struct {def}");
191-
update_function(name, generic_params.map(|g| g.clone_for_update()), &used_param_list, n_new_lifetimes).unwrap();
187+
update_function(&mut editor, name, generic_params.map(|g| g.clone_for_update()), &used_param_list, n_new_lifetimes).unwrap();
192188
tracing::info!("extract_struct_from_function_signature: updating function signature and parameter uses");
189+
editor.add_mappings(make.finish_with_mappings());
190+
builder.add_file_edits(ctx.vfs_file_id(), editor);
193191
},
194192
)
195193
}
@@ -215,6 +213,7 @@ fn extract_field_list(
215213
}
216214

217215
fn update_function(
216+
editor: &mut SyntaxEditor,
218217
name: ast::Name,
219218
generics: Option<ast::GenericParamList>,
220219
used_param_list: &[ast::Param],
@@ -256,11 +255,13 @@ fn update_function(
256255

257256
// it is fine to unwrap() to because there is at least one parameter (if there is no parameters
258257
// the code action will not show)
259-
let start_idx = used_param_list.first().unwrap().syntax().index();
260-
let end_idx = used_param_list.last().unwrap().syntax().index();
261-
let used_params_range = start_idx..end_idx + 1;
258+
let start_idx = used_param_list.first().unwrap().syntax();
259+
let end_idx = used_param_list.last().unwrap().syntax();
262260
let new = vec![param.syntax().syntax_element()];
263-
used_param_list.first().unwrap().syntax().parent()?.splice_children(used_params_range, new);
261+
editor.replace_all(
262+
NodeOrToken::Node(start_idx.clone())..=NodeOrToken::Node(end_idx.clone()),
263+
new,
264+
);
264265
// no need update uses of parameters in function, because we destructure the struct
265266
Some(())
266267
}
@@ -271,6 +272,7 @@ fn pat_to_name(pat: ast::Pat) -> Option<ast::Name> {
271272
}
272273
}
273274
fn create_struct_def(
275+
editor: &SyntaxFactory,
274276
name: ast::Name,
275277
func: &ast::Fn,
276278
param_ast: &[ast::Param],
@@ -279,36 +281,20 @@ fn create_struct_def(
279281
) -> ast::Struct {
280282
let fn_vis = func.visibility();
281283

282-
let insert_vis = |node: &'_ SyntaxNode, vis: &'_ SyntaxNode| {
283-
let vis = vis.clone_for_update();
284-
ted::insert(ted::Position::before(node), vis);
285-
};
286-
287-
// for fields without any existing visibility, use visibility of enum
288-
let field_list = {
289-
if let Some(vis) = &fn_vis {
290-
field_list
291-
.fields()
292-
.filter(|field| field.visibility().is_none())
293-
.filter_map(|field| field.name())
294-
.for_each(|it| insert_vis(it.syntax(), vis.syntax()));
295-
}
296-
297-
field_list
298-
};
299284
// if we do not expleictly copy over comments/attribures they just get lost
300285
// TODO: what about comments/attributes in between parameters
301-
param_ast.iter().zip(field_list.fields()).for_each(|(param, field)| {
302-
let elements = take_all_comments(param.clone());
303-
ted::insert_all(ted::Position::first_child_of(field.syntax()), elements);
304-
ted::insert_all(
305-
ted::Position::first_child_of(field.syntax()),
306-
param
307-
.attrs()
308-
.flat_map(|it| [it.syntax().clone().into(), make::tokens::single_newline().into()])
309-
.collect(),
310-
);
311-
});
286+
// param_ast.iter().zip(field_list.fields()).for_each(|(param, field)| {
287+
// editor.attr_inner(meta)
288+
// let elements = take_all_comments(param.clone());
289+
// editor.insert_all(Position::first_child_of(field.syntax()), elements);
290+
// editor.insert_all(
291+
// Position::first_child_of(field.syntax()),
292+
// param
293+
// .attrs()
294+
// .flat_map(|it| [it.syntax().clone().into(), make::tokens::single_newline().into()])
295+
// .collect(),
296+
// );
297+
// });
312298
let field_list = field_list.indent(IndentLevel::single());
313299

314300
make::struct_(fn_vis, name, generics, field_list.into()).clone_for_update()
@@ -381,6 +367,7 @@ fn contains_impl_trait(ty: &ast::Type) -> bool {
381367
ty.syntax().descendants().any(|ty| ty.kind() == ast::ImplTraitType::kind())
382368
}
383369
fn generate_new_lifetimes(
370+
editor: &mut SyntaxEditor,
384371
ty: &ast::Type,
385372
existing_type_param_list: &mut Option<ast::GenericParamList>,
386373
) -> Option<()> {
@@ -395,15 +382,15 @@ fn generate_new_lifetimes(
395382
.get_or_insert(make::generic_param_list(std::iter::empty()).clone_for_update())
396383
.add_generic_param(make::lifetime_param(new_lt.clone()).clone_for_update().into());
397384

398-
ted::replace(lt.syntax(), new_lt.clone_for_update().syntax());
385+
// editor.replace(lt.syntax(), new_lt.clone_for_update().syntax());
399386
} else if let Some(r) = ast::RefType::cast(token.clone())
400387
&& r.lifetime().is_none()
401388
{
402389
let new_lt = generate_unique_lifetime_param_name(existing_type_param_list)?;
403390
existing_type_param_list
404391
.get_or_insert(make::generic_param_list(std::iter::empty()).clone_for_update())
405392
.add_generic_param(make::lifetime_param(new_lt.clone()).clone_for_update().into());
406-
ted::insert(ted::Position::after(r.amp_token()?), new_lt.clone_for_update().syntax());
393+
// editor.insert(Position::after(r.amp_token()?), new_lt.clone_for_update().syntax());
407394
}
408395
// TODO: nominal types that have only lifetimes
409396
// struct Bar<'a, 'b> { f: &'a &'b i32 }
@@ -423,12 +410,12 @@ fn tag_generics_in_function_signature(
423410
ast::GenericParam::LifetimeParam(lt)
424411
if matches!(token.kind(), T![lifetime_ident]) =>
425412
{
426-
if let Some(lt) = lt.lifetime() {
427-
if lt.text().as_str() == token.text() {
428-
*tag = true;
429-
tagged_one = true;
430-
break;
431-
}
413+
if let Some(lt) = lt.lifetime()
414+
&& lt.text().as_str() == token.text()
415+
{
416+
*tag = true;
417+
tagged_one = true;
418+
break;
432419
}
433420
}
434421
param if matches!(token.kind(), T![ident]) => {
@@ -485,7 +472,6 @@ fn existing_definition(
485472

486473
fn process_references(
487474
ctx: &AssistContext<'_>,
488-
builder: &mut SourceChangeBuilder,
489475
visited_modules: &mut FxHashSet<Module>,
490476
function_module_def: &ModuleDef,
491477
refs: Vec<FileReference>,
@@ -497,8 +483,6 @@ fn process_references(
497483
refs.into_iter()
498484
.flat_map(|reference| {
499485
let (call, scope_node, module) = reference_to_node(&ctx.sema, reference)?;
500-
let scope_node = builder.make_syntax_mut(scope_node);
501-
let call = builder.make_mut(call);
502486
if !visited_modules.contains(&module) {
503487
let mod_path = module.find_use_path(
504488
ctx.sema.db,
@@ -538,15 +522,20 @@ fn reference_to_node(
538522
}
539523

540524
fn apply_references(
525+
builder: &mut SourceChangeBuilder,
541526
insert_use_cfg: InsertUseConfig,
542527
call: CallExpr,
543528
import: Option<(ImportScope, hir::ModPath)>,
544529
edition: Edition,
545530
used_params_range: Range<usize>,
546531
field_list: &ast::RecordFieldList,
547532
name: ast::Name,
533+
file_id: impl Into<FileId>,
548534
) -> Option<()> {
535+
let make = SyntaxFactory::with_mappings();
536+
let mut editor = builder.make_editor(call.syntax());
549537
if let Some((scope, path)) = import {
538+
let scope = builder.make_import_scope_mut(scope);
550539
insert_use(&scope, mod_path_to_ast(&path, edition), &insert_use_cfg);
551540
}
552541

@@ -570,18 +559,24 @@ fn apply_references(
570559
})
571560
.collect::<Option<Vec<_>>>()?,
572561
);
562+
let first = call.arg_list()?.args().nth(match call {
563+
// for some reason the indices for parameters of method go in increments of 3s (but
564+
// start at 4 to accommodate the self parameter)
565+
CallExpr::Method(_) => used_params_range.start / 3 - 1,
566+
CallExpr::Normal(_) => used_params_range.start - 1,
567+
})?;
568+
let last = call.arg_list()?.args().nth(match call {
569+
// for some reason the indices for parameters of method go in increments of 3s (but
570+
// start at 4 to accommodate the self parameter)
571+
CallExpr::Method(_) => used_params_range.end / 3 - 1,
572+
CallExpr::Normal(_) => used_params_range.end / 3,
573+
})?;
573574
let record_expr = make::record_expr(path, fields).clone_for_update();
574-
575-
// range for method definition used parames seems to be off
576-
call.arg_list()?.syntax().splice_children(
577-
match call {
578-
// but at call sites methods don't include the self argument as part of the "arg list" so
579-
// we have to decduct one parameters (for some reason length 3) from range
580-
CallExpr::Method(_) => (used_params_range.start - 3)..(used_params_range.end - 3),
581-
CallExpr::Normal(_) => used_params_range,
582-
},
575+
editor.replace_all(
576+
NodeOrToken::Node(first.syntax().clone())..=NodeOrToken::Node(last.syntax().clone()),
583577
vec![record_expr.syntax().syntax_element()],
584578
);
579+
builder.add_file_edits(file_id, editor);
585580
Some(())
586581
}
587582

0 commit comments

Comments
 (0)