Skip to content

Commit 6d35425

Browse files
committed
feat: extract_struct_from_function_signature
working on making updating references. also fixed typos fixed and added more tests
1 parent d0029b2 commit 6d35425

File tree

2 files changed

+260
-18
lines changed

2 files changed

+260
-18
lines changed

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

Lines changed: 259 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1-
use hir::{Function, ModuleDef};
2-
use ide_db::{RootDatabase, assists::AssistId, path_transform::PathTransform};
1+
use std::ops::Range;
2+
3+
use hir::{Function, HasCrate, Module, ModuleDef};
4+
use ide_db::{
5+
FxHashSet, RootDatabase,
6+
assists::AssistId,
7+
defs::Definition,
8+
helpers::mod_path_to_ast,
9+
imports::insert_use::{ImportScope, InsertUseConfig, insert_use},
10+
path_transform::PathTransform,
11+
search::FileReference,
12+
source_change::SourceChangeBuilder,
13+
};
314
use itertools::Itertools;
415
use syntax::{
5-
AstNode, SyntaxElement, SyntaxKind, SyntaxNode, T,
16+
AstNode, Edition, SyntaxElement, SyntaxKind, SyntaxNode, T,
617
ast::{
7-
self, HasAttrs, HasGenericParams, HasName, HasVisibility,
18+
self, CallExpr, HasArgList, HasAttrs, HasGenericParams, HasName, HasVisibility,
19+
RecordExprField,
820
edit::{AstNodeEdit, IndentLevel},
921
make,
1022
},
@@ -24,7 +36,7 @@ use crate::{AssistContext, Assists};
2436
// ```
2537
// struct FooStruct{ bar: u32, baz: u32 }
2638
//
27-
// fn foo(foo_struct: FooStruct) { ... }
39+
// fn foo(FooStruct { bar, baz, .. }: FooStruct) { ... }
2840
// ```
2941

3042
pub(crate) fn extract_struct_from_function_signature(
@@ -50,7 +62,7 @@ pub(crate) fn extract_struct_from_function_signature(
5062
return None;
5163
}
5264

53-
// TODO: special handiling for self?
65+
// TODO: special handling for self?
5466
// TODO: special handling for destrutered types (or maybe just don't support code action on
5567
// destructed types yet
5668

@@ -68,20 +80,68 @@ pub(crate) fn extract_struct_from_function_signature(
6880
})
6981
.collect::<Option<Vec<_>>>()?,
7082
);
83+
84+
let start_index = used_param_list.first()?.syntax().index();
85+
let end_index = used_param_list.last()?.syntax().index();
86+
let used_params_range = start_index..end_index + 1;
7187
acc.add(
7288
AssistId::refactor_rewrite("extract_struct_from_function_signature"),
7389
"Extract struct from signature of a function",
7490
target,
7591
|builder| {
92+
let edition = fn_hir.krate(ctx.db()).edition(ctx.db());
93+
let enum_module_def = ModuleDef::from(fn_hir);
94+
95+
let usages = Definition::Function(fn_hir).usages(&ctx.sema).all();
96+
let mut visited_modules_set = FxHashSet::default();
97+
let current_module = fn_hir.module(ctx.db());
98+
visited_modules_set.insert(current_module);
99+
// record file references of the file the def resides in, we only want to swap to the edited file in the builder once
100+
101+
let mut def_file_references = None;
102+
103+
for (file_id, references) in usages {
104+
if file_id == ctx.file_id() {
105+
def_file_references = Some(references);
106+
continue;
107+
}
108+
builder.edit_file(file_id.file_id(ctx.db()));
109+
let processed = process_references(
110+
ctx,
111+
builder,
112+
&mut visited_modules_set,
113+
&enum_module_def,
114+
references,
115+
name.clone()
116+
);
117+
processed.into_iter().for_each(|(path, node, import)| {
118+
apply_references(ctx.config.insert_use, path, node, import, edition, used_params_range.clone(), &field_list);
119+
});
120+
}
121+
76122
// TODO: update calls to the function
77123
tracing::info!("extract_struct_from_function_signature: starting edit");
78124
builder.edit_file(ctx.vfs_file_id());
79-
// this has to be after the edit_file (order matters)
80-
// fn_ast and param_list must be "mut" for the effect to work on used_param_lsit
81-
let fn_ast = builder.make_mut(fn_ast);
82-
let param_list = builder.make_mut(param_list);
125+
let fn_ast_mut = builder.make_mut(fn_ast.clone());
126+
builder.make_mut(param_list.clone());
83127
let used_param_list = used_param_list.into_iter().map(|p| builder.make_mut(p)).collect_vec();
84128
tracing::info!("extract_struct_from_function_signature: editing main file");
129+
// this has to be after the edit_file (order matters)
130+
// fn_ast and param_list must be "mut" for the effect to work on used_param_list
131+
if let Some(references) = def_file_references {
132+
let processed = process_references(
133+
ctx,
134+
builder,
135+
&mut visited_modules_set,
136+
&enum_module_def,
137+
references,
138+
name.clone()
139+
);
140+
processed.into_iter().for_each(|(path, node, import)| {
141+
apply_references(ctx.config.insert_use, path, node, import, edition, used_params_range.clone(), &field_list);
142+
});
143+
}
144+
85145

86146
let generic_params = fn_ast
87147
.generic_param_list()
@@ -112,15 +172,15 @@ pub(crate) fn extract_struct_from_function_signature(
112172
field_list.clone_for_update()
113173
};
114174
tracing::info!("extract_struct_from_function_signature: collecting fields");
115-
let def = create_struct_def(name.clone(), &fn_ast, &used_param_list, &field_list, generics);
175+
let def = create_struct_def(name.clone(), &fn_ast_mut, &used_param_list, &field_list, generics);
116176
tracing::info!("extract_struct_from_function_signature: creating struct");
117177

118-
let indent = fn_ast.indent_level();
178+
let indent = fn_ast_mut.indent_level();
119179
let def = def.indent(indent);
120180

121181

122182
ted::insert_all(
123-
ted::Position::before(fn_ast.syntax()),
183+
ted::Position::before(fn_ast_mut.syntax()),
124184
vec![
125185
def.syntax().clone().into(),
126186
make::tokens::whitespace(&format!("\n\n{indent}")).into(),
@@ -164,9 +224,11 @@ fn update_function(
164224
.clone_for_update();
165225
// TODO: will eventually need to handle self too
166226

167-
let range = used_param_list.first()?.syntax().syntax_element()
168-
..=used_param_list.last()?.syntax().syntax_element();
169-
ted::replace_all(range, vec![param.syntax().syntax_element()]);
227+
let start_index = used_param_list.first().unwrap().syntax().index();
228+
let end_index = used_param_list.last().unwrap().syntax().index();
229+
let used_params_range = start_index..end_index + 1;
230+
let new = vec![param.syntax().syntax_element()];
231+
used_param_list.first()?.syntax().parent()?.splice_children(used_params_range, new);
170232
// no need update uses of parameters in function, because we destructure the struct
171233
Some(())
172234
}
@@ -334,6 +396,104 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Fu
334396
.any(|(name, _)| name.as_str() == variant_name.text().trim_start_matches("r#"))
335397
}
336398

399+
fn process_references(
400+
ctx: &AssistContext<'_>,
401+
builder: &mut SourceChangeBuilder,
402+
visited_modules: &mut FxHashSet<Module>,
403+
function_module_def: &ModuleDef,
404+
refs: Vec<FileReference>,
405+
name: ast::Name,
406+
) -> Vec<(ast::PathSegment, SyntaxNode, Option<(ImportScope, hir::ModPath)>)> {
407+
// we have to recollect here eagerly as we are about to edit the tree we need to calculate the changes
408+
// and corresponding nodes up front
409+
let name = make::name_ref(name.text_non_mutable());
410+
refs.into_iter()
411+
.flat_map(|reference| {
412+
let (segment, scope_node, module) =
413+
reference_to_node(&ctx.sema, reference, name.clone())?;
414+
let scope_node = builder.make_syntax_mut(scope_node);
415+
if !visited_modules.contains(&module) {
416+
let mod_path = module.find_use_path(
417+
ctx.sema.db,
418+
*function_module_def,
419+
ctx.config.insert_use.prefix_kind,
420+
ctx.config.import_path_config(),
421+
);
422+
if let Some(mut mod_path) = mod_path {
423+
mod_path.pop_segment();
424+
mod_path.push_segment(hir::Name::new_root(name.text_non_mutable()).clone());
425+
let scope = ImportScope::find_insert_use_container(&scope_node, &ctx.sema)?;
426+
visited_modules.insert(module);
427+
return Some((segment, scope_node, Some((scope, mod_path))));
428+
}
429+
}
430+
Some((segment, scope_node, None))
431+
})
432+
.collect()
433+
}
434+
fn reference_to_node(
435+
sema: &hir::Semantics<'_, RootDatabase>,
436+
reference: FileReference,
437+
name: ast::NameRef,
438+
) -> Option<(ast::PathSegment, SyntaxNode, hir::Module)> {
439+
// filter out the reference in macro
440+
let segment =
441+
reference.name.as_name_ref()?.syntax().parent().and_then(ast::PathSegment::cast)?;
442+
443+
let segment_range = segment.syntax().text_range();
444+
if segment_range != reference.range {
445+
return None;
446+
}
447+
448+
let parent = segment.parent_path().syntax().parent()?;
449+
let expr_or_pat = match_ast! {
450+
match parent {
451+
ast::PathExpr(_it) => parent.parent()?,
452+
ast::RecordExpr(_it) => parent,
453+
ast::TupleStructPat(_it) => parent,
454+
ast::RecordPat(_it) => parent,
455+
_ => return None,
456+
}
457+
};
458+
let module = sema.scope(&expr_or_pat)?.module();
459+
let segment = make::path_segment(name);
460+
Some((segment, expr_or_pat, module))
461+
}
462+
463+
fn apply_references(
464+
insert_use_cfg: InsertUseConfig,
465+
segment: ast::PathSegment,
466+
node: SyntaxNode,
467+
import: Option<(ImportScope, hir::ModPath)>,
468+
edition: Edition,
469+
used_params_range: Range<usize>,
470+
field_list: &ast::RecordFieldList,
471+
) -> Option<()> {
472+
if let Some((scope, path)) = import {
473+
insert_use(&scope, mod_path_to_ast(&path, edition), &insert_use_cfg);
474+
}
475+
// deep clone to prevent cycle
476+
let path = make::path_from_segments(std::iter::once(segment.clone_subtree()), false);
477+
let call = CallExpr::cast(node)?;
478+
let fields = make::record_expr_field_list(
479+
call.arg_list()?
480+
.args()
481+
.skip(used_params_range.start - 1)
482+
.take(used_params_range.end - used_params_range.start)
483+
.zip(field_list.fields())
484+
.map(|e| {
485+
e.1.name().map(|name| -> RecordExprField {
486+
make::record_expr_field(make::name_ref(name.text_non_mutable()), Some(e.0))
487+
})
488+
})
489+
.collect::<Option<Vec<_>>>()?,
490+
);
491+
let record_expr = make::record_expr(path, fields).clone_for_update();
492+
call.arg_list()?
493+
.syntax()
494+
.splice_children(used_params_range, vec![record_expr.syntax().syntax_element()]);
495+
Some(())
496+
}
337497
#[cfg(test)]
338498
mod tests {
339499
use super::*;
@@ -351,7 +511,7 @@ fn one($0x: u8, y: u32) {}
351511
);
352512
}
353513
#[test]
354-
fn test_extract_function_signature_single_parameters() {
514+
fn test_extract_function_signature_single_parameter() {
355515
check_assist(
356516
extract_struct_from_function_signature,
357517
r#"
@@ -378,4 +538,86 @@ fn foo(FooStruct { bar, baz, .. }: FooStruct) {}
378538
"#,
379539
);
380540
}
541+
#[test]
542+
fn test_extract_function_signature_all_parameters_with_reference() {
543+
check_assist(
544+
extract_struct_from_function_signature,
545+
r#"
546+
fn foo($0bar: i32, baz: i32$0) {}
547+
548+
fn main() {
549+
foo(1, 2)
550+
}
551+
"#,
552+
r#"
553+
struct FooStruct{ bar: i32, baz: i32 }
554+
555+
fn foo(FooStruct { bar, baz, .. }: FooStruct) {}
556+
557+
fn main() {
558+
foo(FooStruct { bar: 1, baz: 2 })
559+
}
560+
"#,
561+
);
562+
}
563+
#[test]
564+
fn test_extract_function_signature_single_parameter_with_reference_seperate_and_inself() {
565+
check_assist(
566+
extract_struct_from_function_signature,
567+
r#"
568+
mod a {
569+
pub fn foo($0bar: i32$0, baz: i32) {
570+
foo(1, 2)
571+
}
572+
}
573+
574+
mod b {
575+
use crate::a::foo;
576+
577+
fn main() {
578+
foo(1, 2)
579+
}
580+
}
581+
"#,
582+
r#"
583+
mod a {
584+
pub struct FooStruct{ pub bar: i32 }
585+
586+
pub fn foo(FooStruct { bar, .. }: FooStruct, baz: i32) {
587+
foo(FooStruct { bar: 1 }, 2)
588+
}
589+
}
590+
591+
mod b {
592+
use crate::a::{foo, FooStruct};
593+
594+
fn main() {
595+
foo(FooStruct { bar: 1 }, 2)
596+
}
597+
}
598+
"#,
599+
);
600+
}
601+
#[test]
602+
fn test_extract_function_signature_single_parameter_with_reference() {
603+
check_assist(
604+
extract_struct_from_function_signature,
605+
r#"
606+
fn foo($0bar: i32$0, baz: i32) {}
607+
608+
fn main() {
609+
foo(1, 2)
610+
}
611+
"#,
612+
r#"
613+
struct FooStruct{ bar: i32 }
614+
615+
fn foo(FooStruct { bar, .. }: FooStruct, baz: i32) {}
616+
617+
fn main() {
618+
foo(FooStruct { bar: 1 }, 2)
619+
}
620+
"#,
621+
);
622+
}
381623
}

crates/ide-assists/src/tests/generated.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1197,7 +1197,7 @@ fn foo($0bar: u32, baz: u32$0) { ... }
11971197
r#####"
11981198
struct FooStruct{ bar: u32, baz: u32 }
11991199
1200-
fn foo(foo_struct: FooStruct) { ... }
1200+
fn foo(FooStruct { bar, baz, .. }: FooStruct) { ... }
12011201
"#####,
12021202
)
12031203
}

0 commit comments

Comments
 (0)