Skip to content

Add ide-assist: extract_struct_from_function_signature #20343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from

Conversation

mendelsshop
Copy link

@mendelsshop mendelsshop commented Jul 30, 2025

When you select the signature of a function, you should have the ability to extract all or part of the function's parameters to a struct.

fn foo(bar: u32, baz: u32) { ... }

->

struct FooStruct {
     bar: u32,
     baz: u32,
}

fn foo(FooStruct) { ... }

Still very much a work in progress.

Closes: #20331

@mendelsshop
Copy link
Author

I have a few design decisions that I do not know what I should do about:

  • Functions support anonymous lifetimes, but structs do not so I should generate a new lifetime for every anonymous lifetime I see?

  • Should a self parameter be extracted to the struct? What if the self parameter is fully selected?

  • When I replace the signature in the original function, I could either 1) destructure the struct in the signature or 2) go around the whole function, updating all the uses.

fn foo(mut i: i32, j: i32) {
    i = j
}

->
1)

struct FooStruct{ i: i32, j: i32 }

fn foo(FooStruct { mut i, j }: FooStruct) {
    i = j
}

or 2)

struct FooStruct{ i: i32, j: i32 }

fn foo(mut foo_struct FooStruct) {
    foo_struct.i = foo_struct.j
}
  • I would like there to be a way to just select a certain part of the signature and only extract that part to a new struct. If one character is selected, I want to assume that the user wants the whole function signature extracted to a struct, would that be a good way to differentiate whether to extract the whole signature or only a part of it?

  • I am also not planning on supporting destructred patterns like:

fn foo((a, b): (i32, i32)) {}

@mendelsshop
Copy link
Author

Also, I think CI is failing because the slow tests need to be updated.
I could not figure out how to update them.

@ChayimFriedman2
Copy link
Contributor

  • Functions support anonymous lifetimes, but structs do not so I should generate a new lifetime for every anonymous lifetime I see?

Lifetime support in r-a is very incomplete. Ideally I think we'd generate one lifetime for each invariant lifetime, and another one for all the covariant. We can't do it now so I think the best will be to always generate a single lifetime.

  • When I replace the signature in the original function, I could either 1) destructure the struct in the signature or 2) go around the whole function, updating all the uses.

Assists should focus on one change. Therefore, I think we should destructure the parameter, and have another assist to convert to dot access.

Although... Destructuring in parameters isn't that common in idiomatic Rust, so I'm not sure. @rust-lang/rust-analyzer thoughts?

  • I would like there to be a way to just select a certain part of the signature and only extract that part to a new struct. If one character is selected, I want to assume that the user wants the whole function signature extracted to a struct, would that be a good way to differentiate whether to extract the whole signature or only a part of it?

I think the assist should only be enabled when there is a selection, not on empty selection, that's too much clutter. This way you also don't have to fix the slow tests.

  • I am also not planning on supporting destructred patterns like:

Feel free. This can always be added later if desired.

  • Should a self parameter be extracted to the struct? What if the self parameter is fully selected?

Given what I said about selection, I think it should be treated like any other parameter, i.e. put it only if it's selected.

@mendelsshop
Copy link
Author

mendelsshop commented Aug 1, 2025

What should I do about impl trait, my assumption is that just don't provide the assist then.

Also, when the struct has generics, the way I am doing it now only the function signature fully specifies all the generics while uses, rely on inference from the caller.

fn foo<'a>($0bar: &'_ &'a i32$0, baz: i32) {
    foo(bar, baz)
}

->

struct FooStruct<'a, 'b>{ bar: &'b &'a i32 }
// signature (all generics specified)
fn foo<'a>(FooStruct { bar, .. }: FooStruct<'a, '_>, baz: i32) {
    // use (bare struct name without any generics specified)
    foo(FooStruct { bar: bar }, baz)
}

@mendelsshop
Copy link
Author

I think I won't support doing it for self parameters, as that would be too much of a change. You have to possibly move the function out of the impl block and update all the references to be normal function calls. And this can be done in two steps, first by renaming self and the using this code assist.

Also, should I be copying relevant where clauses? My reason not to do that is that I think it is common to put where clauses on functions and leave the types alone. But when we copy the generics parameters from the function we also copy any bound that are on the generics directly, like <A: Copy>.

@mendelsshop
Copy link
Author

It's mostly working, there are a few things I could not figure out how to do:

If there is a comment in between two parameters, I cannot move into the struct definition between the corresponding fields.

Also for a case like this:

struct Foo<'a> { baz: &'a i32 }
fn bar(foo: Foo) {}

How do I detect that the Foo type in bar is missing lifetime arguments so I can add explicit lifetimes for it?

And I assume I should remove the "feat: extract_struct_from_function_signature" header from all my commit messages?

@mendelsshop mendelsshop marked this pull request as ready for review August 4, 2025 21:01
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 4, 2025
@rustbot

This comment has been minimized.

@rustbot rustbot added has-merge-commits S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Aug 4, 2025
now actually uses the parameter name for field.
and generates correct struct name.
only capture comments and attributes around the parameters
added some info logging
trying to debug salsa query error
fixed issue with resolving generics (I think)
started writing the code to update the original function
code action only avaliable in signature part of a function

for some reason still does not change anything in the file
made the edit actually have a effect by putting the edit_file first
made the parameter in original function lower snake case
made the checking if pre existing struct with new name use the new name
as opposed to the original function name
fixed autogenerated test
now destructures struct in function signature
and only add paramereters that are selected to the struct
added another test
working on making updating references.
also fixed typos
fixed and added more tests
starting to work on lifetime part
the signature of the function now uses the struct with all the generic
args

while uses don't specifiy any generics
do not support if there is impl trait

also ideas for when handling self parameter
cleaned up comments
made it work for normal parameters in methods
cleanup and follow stye guide a bit
fixed bug where if extracting from function/method in impl
and was referenced somewhere else in impl it would break down b/c making
the impl mut would happen after the reference was updated
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)
@mendelsshop mendelsshop force-pushed the feat-extract-struct-from-function-signature branch from 2a07efa to a1bb839 Compare August 4, 2025 21:01
@rustbot rustbot removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Aug 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Assist: Extract struct from function signature
3 participants