Skip to content

Commit 26ba1b9

Browse files
committed
feat: extract_struct_from_function_signature
mostly copies attributes and comments properly (edge case where comment in between to parameters is lost) reference types with no lifetimes now get an autogenerated lifetime (still need to figure out how to do it for types with only lifetimes that do not have explicit lifetimes)
1 parent a6b57c2 commit 26ba1b9

File tree

1 file changed

+77
-27
lines changed

1 file changed

+77
-27
lines changed

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

Lines changed: 77 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ pub(crate) fn extract_struct_from_function_signature(
7272
// clauses
7373
// we also need special handling for method calls
7474

75-
// TODO: (future)special handling for destrutered types (or maybe just don't support code action on
75+
// TODO: (future)special handling for destrutered types (right now we don't support code action on
7676
// destructed types yet
7777

7878
let field_list = extract_field_list(&func, &used_param_list)?;
@@ -285,7 +285,7 @@ fn create_struct_def(
285285
};
286286

287287
// for fields without any existing visibility, use visibility of enum
288-
let field_list: ast::FieldList = {
288+
let field_list = {
289289
if let Some(vis) = &fn_vis {
290290
field_list
291291
.fields()
@@ -294,39 +294,34 @@ fn create_struct_def(
294294
.for_each(|it| insert_vis(it.syntax(), vis.syntax()));
295295
}
296296

297-
field_list.clone().into()
297+
field_list
298298
};
299+
// if we do not expleictly copy over comments/attribures they just get lost
300+
// 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+
});
299312
let field_list = field_list.indent(IndentLevel::single());
300313

301-
let strukt = make::struct_(fn_vis, name, generics, field_list).clone_for_update();
302314

303-
// take comments from only inside signature
304-
ted::insert_all(
305-
ted::Position::first_child_of(strukt.syntax()),
306-
take_all_comments(param_ast.iter()),
307-
);
308315

309-
// TODO: this may not be correct as we shouldn't put all the attributes at the top
310-
// copy attributes from each parameter
311-
ted::insert_all(
312-
ted::Position::first_child_of(strukt.syntax()),
313-
param_ast
314-
.iter()
315-
.flat_map(|p| p.attrs())
316-
.flat_map(|it| {
317-
vec![it.syntax().clone_for_update().into(), make::tokens::single_newline().into()]
318-
})
319-
.collect(),
320-
);
321-
322-
strukt
316+
make::struct_(fn_vis, name, generics, field_list.into()).clone_for_update()
323317
}
324318
// Note: this also detaches whitespace after comments,
325319
// since `SyntaxNode::splice_children` (and by extension `ted::insert_all_raw`)
326320
// detaches nodes. If we only took the comments, we'd leave behind the old whitespace.
327-
fn take_all_comments<'a>(node: impl Iterator<Item = &'a ast::Param>) -> Vec<SyntaxElement> {
321+
fn take_all_comments(node: impl ast::AstNode) -> Vec<SyntaxElement> {
328322
let mut remove_next_ws = false;
329-
node.flat_map(|p| p.syntax().children_with_tokens())
323+
node.syntax()
324+
.children_with_tokens()
330325
.filter_map(move |child| match child.kind() {
331326
SyntaxKind::COMMENT => {
332327
remove_next_ws = true;
@@ -375,8 +370,13 @@ fn generate_unique_lifetime_param_name(
375370
fn new_life_time_count(ty: &ast::Type) -> usize {
376371
ty.syntax()
377372
.descendants()
378-
.filter_map(ast::Lifetime::cast)
379-
.filter(|lifetime| lifetime.text() == "'_")
373+
.filter(|t| {
374+
match_ast! { match t {
375+
ast::Lifetime(lt) => lt.text() == "'_",
376+
ast::RefType(r) => r.lifetime().is_none(),
377+
_ => false
378+
}}
379+
})
380380
.count()
381381
}
382382
fn contains_impl_trait(ty: &ast::Type) -> bool {
@@ -387,6 +387,8 @@ fn generate_new_lifetimes(
387387
existing_type_param_list: &mut Option<ast::GenericParamList>,
388388
) -> Option<()> {
389389
for token in ty.syntax().descendants() {
390+
// we do not have to worry about for<'a> because we are only looking at '_ or &Type
391+
// if you have an unbound lifetime thats on you
390392
if let Some(lt) = ast::Lifetime::cast(token.clone())
391393
&& lt.text() == "'_"
392394
{
@@ -396,7 +398,18 @@ fn generate_new_lifetimes(
396398
.add_generic_param(make::lifetime_param(new_lt.clone()).clone_for_update().into());
397399

398400
ted::replace(lt.syntax(), new_lt.clone_for_update().syntax());
401+
} else if let Some(r) = ast::RefType::cast(token.clone())
402+
&& r.lifetime().is_none()
403+
{
404+
let new_lt = generate_unique_lifetime_param_name(existing_type_param_list)?;
405+
existing_type_param_list
406+
.get_or_insert(make::generic_param_list(std::iter::empty()).clone_for_update())
407+
.add_generic_param(make::lifetime_param(new_lt.clone()).clone_for_update().into());
408+
ted::insert(ted::Position::after(r.amp_token()?), new_lt.clone_for_update().syntax());
399409
}
410+
// TODO: nominal types that have only lifetimes
411+
// struct Bar<'a, 'b> { f: &'a &'b i32 }
412+
// fn foo(f: Bar) {}
400413
}
401414
Some(())
402415
}
@@ -782,6 +795,21 @@ fn foo($0bar: &'_ i32$0, baz: i32) {}
782795
r#"
783796
struct FooStruct<'a>{ bar: &'a i32 }
784797
798+
fn foo(FooStruct { bar, .. }: FooStruct<'_>, baz: i32) {}
799+
"#,
800+
);
801+
}
802+
803+
#[test]
804+
fn test_extract_function_signature_single_parameter_with_plain_reference_type() {
805+
check_assist(
806+
extract_struct_from_function_signature,
807+
r#"
808+
fn foo($0bar: &i32$0, baz: i32) {}
809+
"#,
810+
r#"
811+
struct FooStruct<'a>{ bar: &'a i32 }
812+
785813
fn foo(FooStruct { bar, .. }: FooStruct<'_>, baz: i32) {}
786814
"#,
787815
);
@@ -889,4 +917,26 @@ fn bar() {
889917
"#,
890918
);
891919
}
920+
#[test]
921+
fn test_extract_function_signature_in_method_comments_and_attributes() {
922+
check_assist(
923+
extract_struct_from_function_signature,
924+
r#"
925+
fn foo(
926+
#[foo]
927+
// gag
928+
$0f: i32,
929+
) { }
930+
"#,
931+
r#"
932+
struct FooStruct{ #[foo]
933+
// gag
934+
f: i32 }
935+
936+
fn foo(
937+
FooStruct { f, .. }: FooStruct,
938+
) { }
939+
"#,
940+
)
941+
}
892942
}

0 commit comments

Comments
 (0)