Skip to content
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

add lint for recreation of an entire struct #12772

Merged
merged 1 commit into from
Jul 21, 2024

Conversation

phi-gamma
Copy link
Contributor

This lint makes Clippy warn about situations where an owned struct is
essentially recreated by moving all its fields into a new instance of
the struct. The lint is not machine-applicable because the source
struct may have been partially moved.

This lint originated in something I spotted during peer review. While
working on their branch a colleague ended up with a commit where a
function returned a struct that 1:1 replicated one of its owned inputs
from its members. Initially I suspected they hadn’t run their code
through Clippy but AFAICS there is no lint for this situation yet.

changelog: new lint: [redundant_owned_struct_recreation]

New lint checklist

  • [+] Followed [lint naming conventions][lint_naming]
  • [+] Added passing UI tests (including committed .stderr file)
  • [+] cargo test passes locally
  • [+] Executed cargo dev update_lints
  • [+] Added lint documentation
  • [+] Run cargo dev fmt

@rustbot
Copy link
Collaborator

rustbot commented May 7, 2024

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @y21 (or someone else) some time within the next two weeks.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label May 7, 2024
@phi-gamma
Copy link
Contributor Author

Hi, is there a way to tell in a late pass whether one or more
of a struct’s members have been moved? That might enable
machine-applicability of this lint.

@y21
Copy link
Member

y21 commented May 7, 2024

This might be better as an addition to the unnecessary_struct_initialization lint. It has essentially the same idea as this one, except it only emits a warning for Foo { ..foo } (w/o any fields), so it could be improved to also emit a warning if all fields copy from the base.

In particular, this lint also suffers from the same issues that that other lint does, which is that struct reinitialization only requires the fields to be Copy, whereas this lint's suggestion forces a move of the whole struct, which, if the value is later used again doesn't work if the struct is not Copy, and is also the reason why that other lint is in nursery (#10547 is the issue).

@phi-gamma
Copy link
Contributor Author

phi-gamma commented May 7, 2024 via email

@y21
Copy link
Member

y21 commented May 7, 2024

I thought about this for a bit and yeah, I think restricting it to only Copy types probably fixes the linked issue, but it might also regress too many "true positives" where a warning would be fine.

Maybe what we could do to avoid false positives while still warning on some useful cases would be to not emit a warning if:

  • the struct itself is not Copy
  • but any of its fields are

My reasoning is that if the struct is not Copy and none of its fields are, then Foo { bar: foo.bar }; also moves the entire struct effectively just like foo; does and in either case no fields are accessible afterwards, so the suggested code shouldn't result in more errors. If however there is one Copy field and one non-Copy field, then Foo { bar: foo.bar, baz: foo.baz }; would still allow using the Copy field whereas foo; again makes it inaccessible.


However I think for the purposes of this PR it would be easier to just not worry about the possible false positives that had already existed and only extend the unnecessary_struct_initialization lint to also catch Foo { bar: foo.bar } (in addition to what it already does).

Fixing those false positives could be done separately and the lint is in the nursery category, so that's fine.

@phi-gamma
Copy link
Contributor Author

phi-gamma commented May 8, 2024 via email

@phi-gamma phi-gamma force-pushed the redundant-struct-recreation branch 7 times, most recently from cdbb160 to b12ccb5 Compare May 21, 2024 12:20
@phi-gamma phi-gamma force-pushed the redundant-struct-recreation branch 8 times, most recently from f9f06b3 to 96d4e0b Compare June 19, 2024 16:18
@phi-gamma
Copy link
Contributor Author

phi-gamma commented Jun 20, 2024 via email

Copy link
Member

@y21 y21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really nicely written and looks like this addition fits into this lint nicely, a few things and this should be good

snippet(cx, base.span, "..").into_owned(),
rustc_errors::Applicability::MachineApplicable,
);
for f in fields.iter().filter(|f| !f.is_shorthand) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just ignoring/filtering out shorthand initialization seems like it would introduce false positives -- can you add this test case?

struct S1 {
  a: i32,
  b: i32
}

let a = 42;
let s = S1 { a: 3, b: 4 };

// don't lint: `a` is not from `s`
let s = S1 { a, b: s.b };

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add a test case involving ranges?

let r = 0..5;
let r = r.start..r.end;

The desugaring to Range { start: r.start, end: r.end } might emit a warning here, which might actually be good and applicable (although the message might be confusing if it mentions structs)

/// When some fields are assigned from a base struct and others individually
/// the lint applies only if the source of the field is the same as the base.
/// This is enforced here by comparing the path of the base expression;
/// needless to say the link only applies if it (or whatever expression it is
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// needless to say the link only applies if it (or whatever expression it is
/// needless to say the lint only applies if it (or whatever expression it is

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, good catch!

Comment on lines +173 to +176
if is_copy(cx, cx.typeck_results().expr_ty(expr_a)) && path_to_local(expr_b).is_some() {
// When the type implements `Copy`, a reference to the new struct works on the
// copy. Using the original would borrow it.
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is pre-existing code (+ a nursery lint) so we can just keep it as is and you can resolve the comment, but this logic doesn't seem entirely correct I think 🤔.
Only the fields need to be Copy for S { ..base }, not the type itself:

struct S1 { a: i32, b: i32 }

let mut s = S1 { a: 0, b: 0 };
(&mut S1 { ..s }).b = 1; // incorrect lint, changing to `&mut s` as suggested fails the assertion
assert_eq!(s.b, 0);

@phi-gamma phi-gamma force-pushed the redundant-struct-recreation branch from 96d4e0b to 78e2854 Compare July 2, 2024 07:41
@y21
Copy link
Member

y21 commented Jul 3, 2024

I'll change the label here since it looks like you're still working on this (based on red CI and the wip commit), you can change it back with @rustbot ready (or if you're stuck, feel free to ask for help).

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Jul 3, 2024
@phi-gamma phi-gamma force-pushed the redundant-struct-recreation branch from 67ca1ca to ea51d98 Compare July 9, 2024 08:30
@phi-gamma phi-gamma force-pushed the redundant-struct-recreation branch 2 times, most recently from 884edc1 to 833d57c Compare July 9, 2024 08:51
@phi-gamma
Copy link
Contributor Author

phi-gamma commented Jul 9, 2024 via email

@phi-gamma
Copy link
Contributor Author

phi-gamma commented Jul 9, 2024 via email

@phi-gamma phi-gamma force-pushed the redundant-struct-recreation branch from 833d57c to 7312851 Compare July 9, 2024 09:02
@phi-gamma
Copy link
Contributor Author

phi-gamma commented Jul 9, 2024 via email

@phi-gamma phi-gamma force-pushed the redundant-struct-recreation branch 2 times, most recently from 4cdf1f7 to 2b13a87 Compare July 9, 2024 11:51
…ields

This lint makes Clippy warn about situations where an owned
struct is essentially recreated by moving all its fields into a
new instance of the struct. Until now this lint only triggered
for structs recreated from a base struct.

NB: The new functionality too will cause false positives for the
situation where a non-copy struct consisting of all copy members
is touched again in subsequent code.
@phi-gamma phi-gamma force-pushed the redundant-struct-recreation branch from 2b13a87 to 296dcf0 Compare July 9, 2024 11:53
@phi-gamma
Copy link
Contributor Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties and removed S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Jul 9, 2024
@y21
Copy link
Member

y21 commented Jul 21, 2024

Sorry for taking so long. This all looks good to me now. And yeah, those other things can be left for follow up PRs.

If you're interested in making a specialized lint message for ranges specifically as a followup, we could call higher::Range::hir and see if that returns Some(_)

Either way, thank you for working on this!

@bors r+

@bors
Copy link
Contributor

bors commented Jul 21, 2024

📌 Commit 296dcf0 has been approved by y21

It is now in the queue for this repository.

@bors
Copy link
Contributor

bors commented Jul 21, 2024

⌛ Testing commit 296dcf0 with merge 1807580...

@bors
Copy link
Contributor

bors commented Jul 21, 2024

☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test
Approved by: y21
Pushing 1807580 to master...

@bors bors merged commit 1807580 into rust-lang:master Jul 21, 2024
8 checks passed
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.

4 participants