Skip to content

Commit 63b8d61

Browse files
committed
feat: extract_struct_from_function_signature
starting to work on lifetime part
1 parent 6d35425 commit 63b8d61

File tree

2 files changed

+199
-13
lines changed

2 files changed

+199
-13
lines changed

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

Lines changed: 166 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ pub(crate) fn extract_struct_from_function_signature(
8989
"Extract struct from signature of a function",
9090
target,
9191
|builder| {
92+
let new_lifetime_count = field_list.fields().filter_map(|f|f.ty()).map(|t|new_life_time_count(&t)).sum();
9293
let edition = fn_hir.krate(ctx.db()).edition(ctx.db());
9394
let enum_module_def = ModuleDef::from(fn_hir);
9495

@@ -115,7 +116,10 @@ pub(crate) fn extract_struct_from_function_signature(
115116
name.clone()
116117
);
117118
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+
apply_references(ctx.config.insert_use, path, node, import, edition, used_params_range.clone(), &field_list,
120+
name.clone(),
121+
new_lifetime_count
122+
);
119123
});
120124
}
121125

@@ -138,7 +142,10 @@ pub(crate) fn extract_struct_from_function_signature(
138142
name.clone()
139143
);
140144
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);
145+
apply_references(ctx.config.insert_use, path, node, import, edition, used_params_range.clone(), &field_list,
146+
name.clone(),
147+
new_lifetime_count
148+
);
142149
});
143150
}
144151

@@ -147,7 +154,7 @@ pub(crate) fn extract_struct_from_function_signature(
147154
.generic_param_list()
148155
.and_then(|known_generics| extract_generic_params(&known_generics, &field_list));
149156
tracing::info!("extract_struct_from_function_signature: collecting generics");
150-
let generics = generic_params.as_ref().map(|generics| generics.clone_for_update());
157+
let mut generics = generic_params.as_ref().map(|generics| generics.clone_for_update());
151158

152159
// resolve GenericArg in field_list to actual type
153160
// we would get a query error from salsa, if we would use the field_list
@@ -171,6 +178,7 @@ pub(crate) fn extract_struct_from_function_signature(
171178
} else {
172179
field_list.clone_for_update()
173180
};
181+
field_list.fields().filter_map(|f|f.ty()).try_for_each(|t|generate_new_lifetimes(&t, &mut generics));
174182
tracing::info!("extract_struct_from_function_signature: collecting fields");
175183
let def = create_struct_def(name.clone(), &fn_ast_mut, &used_param_list, &field_list, generics);
176184
tracing::info!("extract_struct_from_function_signature: creating struct");
@@ -187,7 +195,7 @@ pub(crate) fn extract_struct_from_function_signature(
187195
],
188196
);
189197
tracing::info!("extract_struct_from_function_signature: inserting struct {def}");
190-
update_function(name, generic_params.map(|g| g.clone_for_update()), &used_param_list).unwrap();
198+
update_function(name, generic_params.map(|g| g.clone_for_update()), &used_param_list, new_lifetime_count).unwrap();
191199
tracing::info!("extract_struct_from_function_signature: updating function signature and parameter uses");
192200
},
193201
)
@@ -197,10 +205,19 @@ fn update_function(
197205
name: ast::Name,
198206
generics: Option<ast::GenericParamList>,
199207
used_param_list: &[ast::Param],
208+
new_lifetime_count: usize,
200209
) -> Option<()> {
201-
let generic_args = generics
202-
.filter(|generics| generics.generic_params().count() > 0)
203-
.map(|generics| generics.to_generic_args());
210+
// TODO: add new generics if needed
211+
let generic_args =
212+
generics.filter(|generics| generics.generic_params().count() > 0).map(|generics| {
213+
let args = generics.to_generic_args().clone_for_update();
214+
(0..new_lifetime_count).for_each(|_| {
215+
args.add_generic_arg(
216+
make::lifetime_arg(make::lifetime("'_")).clone_for_update().into(),
217+
)
218+
});
219+
args
220+
});
204221
// FIXME: replace with a `ast::make` constructor
205222
let ty = match generic_args {
206223
Some(generic_args) => make::ty(&format!("{name}{generic_args}")),
@@ -212,6 +229,7 @@ fn update_function(
212229
// makes it easier in that we would not have to update all the uses of the variables in
213230
// the function
214231
ast::Pat::RecordPat(make::record_pat(
232+
// TODO: need to have no turbofish kept lifetimes/generics if
215233
make::path_from_text(name.text_non_mutable()),
216234
used_param_list
217235
.iter()
@@ -328,6 +346,44 @@ fn extract_generic_params(
328346
let generics = generics.into_iter().filter_map(|(param, tag)| tag.then_some(param));
329347
tagged_one.then(|| make::generic_param_list(generics))
330348
}
349+
fn generate_unique_lifetime_param_name(
350+
existing_type_param_list: &Option<ast::GenericParamList>,
351+
) -> Option<ast::Lifetime> {
352+
match existing_type_param_list {
353+
Some(type_params) => {
354+
let used_lifetime_params: FxHashSet<_> =
355+
type_params.lifetime_params().map(|p| p.syntax().text().to_string()).collect();
356+
('a'..='z').map(|it| format!("'{it}")).find(|it| !used_lifetime_params.contains(it))
357+
}
358+
None => Some("'a".to_owned()),
359+
}
360+
.map(|it| make::lifetime(&it))
361+
}
362+
fn new_life_time_count(ty: &ast::Type) -> usize {
363+
ty.syntax()
364+
.descendants()
365+
.filter_map(ast::Lifetime::cast)
366+
.filter(|lifetime| lifetime.text() == "'_")
367+
.count()
368+
}
369+
fn generate_new_lifetimes(
370+
ty: &ast::Type,
371+
existing_type_param_list: &mut Option<ast::GenericParamList>,
372+
) -> Option<()> {
373+
for token in ty.syntax().descendants() {
374+
if let Some(lt) = ast::Lifetime::cast(token.clone())
375+
&& lt.text() == "'_"
376+
{
377+
let new_lt = generate_unique_lifetime_param_name(existing_type_param_list)?;
378+
existing_type_param_list
379+
.get_or_insert(make::generic_param_list(std::iter::empty()).clone_for_update())
380+
.add_generic_param(make::lifetime_param(new_lt.clone()).clone_for_update().into());
381+
382+
ted::replace(lt.syntax(), new_lt.clone_for_update().syntax());
383+
}
384+
}
385+
Some(())
386+
}
331387
fn tag_generics_in_function_signature(
332388
ty: &ast::Type,
333389
generics: &mut [(ast::GenericParam, bool)],
@@ -409,8 +465,7 @@ fn process_references(
409465
let name = make::name_ref(name.text_non_mutable());
410466
refs.into_iter()
411467
.flat_map(|reference| {
412-
let (segment, scope_node, module) =
413-
reference_to_node(&ctx.sema, reference, name.clone())?;
468+
let (segment, scope_node, module) = reference_to_node(&ctx.sema, reference)?;
414469
let scope_node = builder.make_syntax_mut(scope_node);
415470
if !visited_modules.contains(&module) {
416471
let mod_path = module.find_use_path(
@@ -434,7 +489,6 @@ fn process_references(
434489
fn reference_to_node(
435490
sema: &hir::Semantics<'_, RootDatabase>,
436491
reference: FileReference,
437-
name: ast::NameRef,
438492
) -> Option<(ast::PathSegment, SyntaxNode, hir::Module)> {
439493
// filter out the reference in macro
440494
let segment =
@@ -456,8 +510,8 @@ fn reference_to_node(
456510
}
457511
};
458512
let module = sema.scope(&expr_or_pat)?.module();
459-
let segment = make::path_segment(name);
460-
Some((segment, expr_or_pat, module))
513+
514+
Some((segment.clone_for_update(), expr_or_pat, module))
461515
}
462516

463517
fn apply_references(
@@ -468,12 +522,28 @@ fn apply_references(
468522
edition: Edition,
469523
used_params_range: Range<usize>,
470524
field_list: &ast::RecordFieldList,
525+
name: ast::Name,
526+
new_lifetime_count: usize,
471527
) -> Option<()> {
472528
if let Some((scope, path)) = import {
473529
insert_use(&scope, mod_path_to_ast(&path, edition), &insert_use_cfg);
474530
}
531+
// TODO: figure out lifetimes in referecnecs
532+
// becauuse we have to convert from segment being non turbofish, also only need
533+
// generics/lifetimes that are used in struct possibly not all the no ones for the original call
534+
// if no specified lifetimes/generics we just give empty one
535+
// if new_lifetime_count > 0 {
536+
// (0..new_lifetime_count).for_each(|_| {
537+
// segment
538+
// .get_or_create_generic_arg_list()
539+
// .add_generic_arg(make::lifetime_arg(make::lifetime("'_")).clone_for_update().into())
540+
// });
541+
// }
542+
543+
ted::replace(segment.name_ref()?.syntax(), name.clone_for_update().syntax());
475544
// deep clone to prevent cycle
476545
let path = make::path_from_segments(std::iter::once(segment.clone_subtree()), false);
546+
// TODO: do I need to to method call to
477547
let call = CallExpr::cast(node)?;
478548
let fields = make::record_expr_field_list(
479549
call.arg_list()?
@@ -489,6 +559,7 @@ fn apply_references(
489559
.collect::<Option<Vec<_>>>()?,
490560
);
491561
let record_expr = make::record_expr(path, fields).clone_for_update();
562+
492563
call.arg_list()?
493564
.syntax()
494565
.splice_children(used_params_range, vec![record_expr.syntax().syntax_element()]);
@@ -561,7 +632,7 @@ fn main() {
561632
);
562633
}
563634
#[test]
564-
fn test_extract_function_signature_single_parameter_with_reference_seperate_and_inself() {
635+
fn test_extract_function_signature_single_parameter_with_reference_separate_and_in_self() {
565636
check_assist(
566637
extract_struct_from_function_signature,
567638
r#"
@@ -620,4 +691,86 @@ mod b {
620691
"#,
621692
);
622693
}
694+
695+
#[test]
696+
fn test_extract_function_signature_single_parameter_generic() {
697+
check_assist(
698+
extract_struct_from_function_signature,
699+
r#"
700+
fn foo<'a, A>($0bar: &'a A$0, baz: i32) {}
701+
"#,
702+
r#"
703+
struct FooStruct<'a, A>{ bar: &'a A }
704+
705+
fn foo<'a, A>(FooStruct { bar, .. }: FooStruct<'a, A>, baz: i32) {}
706+
"#,
707+
);
708+
}
709+
#[test]
710+
fn test_extract_function_signature_single_parameter_generic_with_reference_in_self() {
711+
check_assist(
712+
extract_struct_from_function_signature,
713+
r#"
714+
fn foo<'a, A>($0bar: &'a A$0, baz: i32) {
715+
foo(1, 2)
716+
}
717+
"#,
718+
r#"
719+
struct FooStruct<'a, A>{ bar: &'a A }
720+
721+
fn foo<'a, A>(FooStruct { bar, .. }: FooStruct<'a, A>, baz: i32) {
722+
foo(FooStruct { bar: 1 }, 2)
723+
}
724+
"#,
725+
);
726+
}
727+
728+
#[test]
729+
fn test_extract_function_signature_single_parameter_anonymous_lifetime() {
730+
check_assist(
731+
extract_struct_from_function_signature,
732+
r#"
733+
fn foo($0bar: &'_ i32$0, baz: i32) {}
734+
"#,
735+
r#"
736+
struct FooStruct<'a>{ bar: &'a i32 }
737+
738+
fn foo(FooStruct { bar, .. }: FooStruct, baz: i32) {}
739+
"#,
740+
);
741+
}
742+
#[test]
743+
fn test_extract_function_signature_single_parameter_anonymous_and_normal_lifetime() {
744+
check_assist(
745+
extract_struct_from_function_signature,
746+
r#"
747+
fn foo<'a>($0bar: &'_ &'a i32$0, baz: i32) {}
748+
"#,
749+
r#"
750+
struct FooStruct<'a, 'b>{ bar: &'b &'a i32 }
751+
752+
fn foo<'a>(FooStruct { bar, .. }: FooStruct<'a, '_>, baz: i32) {}
753+
"#,
754+
);
755+
}
756+
757+
#[test]
758+
fn test_extract_function_signature_single_parameter_anonymous_and_normal_lifetime_with_reference_in_self()
759+
{
760+
check_assist(
761+
extract_struct_from_function_signature,
762+
r#"
763+
fn foo<'a>($0bar: &'_ &'a i32$0, baz: i32) {
764+
foo(bar, baz)
765+
}
766+
"#,
767+
r#"
768+
struct FooStruct<'a, 'b>{ bar: &'b &'a i32 }
769+
770+
fn foo<'a>(FooStruct { bar, .. }: FooStruct<'a, '_>, baz: i32) {
771+
foo(FooStruct { bar: bar }, baz)
772+
}
773+
"#,
774+
);
775+
}
623776
}

crates/syntax/src/ast/edit_in_place.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,40 @@ impl ast::GenericParamList {
380380
make::generic_arg_list(args)
381381
}
382382
}
383+
impl ast::GenericArgList {
384+
pub fn add_generic_arg(&self, generic_arg: ast::GenericArg) {
385+
match self.generic_args().last() {
386+
Some(last_param) => {
387+
let position = Position::after(last_param.syntax());
388+
let elements = vec![
389+
make::token(T![,]).into(),
390+
make::tokens::single_space().into(),
391+
generic_arg.syntax().clone().into(),
392+
];
393+
ted::insert_all(position, elements);
394+
}
395+
None => {
396+
let after_l_angle = Position::after(self.l_angle_token().unwrap());
397+
ted::insert(after_l_angle, generic_arg.syntax());
398+
}
399+
}
400+
}
383401

402+
/// Removes the existing generic param
403+
pub fn remove_generic_arg(&self, generic_arg: ast::GenericArg) {
404+
if let Some(previous) = generic_arg.syntax().prev_sibling() {
405+
if let Some(next_token) = previous.next_sibling_or_token() {
406+
ted::remove_all(next_token..=generic_arg.syntax().clone().into());
407+
}
408+
} else if let Some(next) = generic_arg.syntax().next_sibling() {
409+
if let Some(next_token) = next.prev_sibling_or_token() {
410+
ted::remove_all(generic_arg.syntax().clone().into()..=next_token);
411+
}
412+
} else {
413+
ted::remove(generic_arg.syntax());
414+
}
415+
}
416+
}
384417
impl ast::WhereClause {
385418
pub fn add_predicate(&self, predicate: ast::WherePred) {
386419
if let Some(pred) = self.predicates().last() {

0 commit comments

Comments
 (0)