Skip to content

Prevent duplicating comments and tokens when rewriting trait headers#6834

Open
ytmimi wants to merge 12 commits intorust-lang:mainfrom
ytmimi:issue_6160
Open

Prevent duplicating comments and tokens when rewriting trait headers#6834
ytmimi wants to merge 12 commits intorust-lang:mainfrom
ytmimi:issue_6160

Conversation

@ytmimi
Copy link
Copy Markdown
Contributor

@ytmimi ytmimi commented Mar 20, 2026

Fixes #6160, Fixes #5841

Expand the HeaderPart struct to handle some new header parts like, const, unsafe, and auto and refactor trait header formatting to use HeaderPart.

r? @fee1-dead since you originally added HeaderPart

@rustbot rustbot added the S-waiting-on-review Status: awaiting review from the assignee but also interested parties. label Mar 20, 2026
src/header.rs Outdated
Self::new("", DUMMY_SP)
}

/// Find the `keyword` for the header within the give `span`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm a bit confused by this doc comment: is this function finding a keyword, or setting/creating one? (extra context: I'm not too familiar with how Spans work, so I may just be completely misunderstanding)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

A span refers to a byte range in the source code, and each AST node has a span that denotes where it begins and ends. So we're actually searching for the keyword within the span and finding out what it's sub-span is.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

With that in mind, do you have any suggestions to make that clearer?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A span refers to a byte range in the source code, and each AST node has a span that denotes where it begins and ends. So we're actually searching for the keyword within the span and finding out what it's sub-span is.

Thanks! I only just now realised span was added as part of this changeset 🤦

With that in mind, do you have any suggestions to make that clearer?

Maybe the method could be span_at or span_on following the pattern of span_before/span_after?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

span_at Sounds good to me. I'll make that update.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the entire set of method names is not so great. I would support the following set of naming:

  • subspan - currently named as span
  • subspan_from - currently named as span_after
  • subspan_until - currently named as span_before

Appreciate other options as well!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

on second glance, after seems better than from, since from appears to include the given needle. Hmm I will have to think more on this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@fee1-dead i agree. I think renaming those methods would be ideal, though I'm not sure that should happen in this PR. I'd say span_before and span_after should probably be renamed to pos_before and pos_after since they return BytePos objects instead of Spans

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I can open up a follow up PR with any renames based on what we decide to move forward with

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

renamed span -> subspan

@matthewhughes934
Copy link
Copy Markdown
Contributor

matthewhughes934 commented Mar 22, 2026

I think comments between the literal trait and the identity will now be removed:

$ echo 'pub /* first */ trait /* second */ MyTrait /* third */ {}' | cargo run --bin rustfmt --quiet
pub /* first */ trait MyTrait /* third */ {}

Because that second comment is now in a gap not covered by this change:

  • The first comment is captured by format_header
  • The third comment is captured by the updated else block in this change
  • But the second comment (covered by the old 'else' block) is now skipped

src/utils.rs Outdated
Comment on lines +120 to +121
// FIXME(ytmimi) might want to get rid of `format_auto` since we now have `HeaderPart::auto`
#[allow(unused)]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would encourage you to remove this in this PR :) of course it will still be stored in git history, but cleanup would be nice!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

removed

@ytmimi
Copy link
Copy Markdown
Contributor Author

ytmimi commented Mar 23, 2026

I think comments between the literal trait and the identity will now be removed:

$ echo 'pub /* first */ trait /* second */ MyTrait /* third */ {}' | cargo run --bin rustfmt --quiet
pub /* first */ trait MyTrait /* third */ {}

Because that second comment is now in a gap not covered by this change:

  • The first comment is captured by format_header
  • The third comment is captured by the updated else block in this change
  • But the second comment (covered by the old 'else' block) is now skipped

Will Look into this case

@rustbot

This comment has been minimized.

@ytmimi
Copy link
Copy Markdown
Contributor Author

ytmimi commented Mar 23, 2026

I think comments between the literal trait and the identity will now be removed:

$ echo 'pub /* first */ trait /* second */ MyTrait /* third */ {}' | cargo run --bin rustfmt --quiet
pub /* first */ trait MyTrait /* third */ {}

Because that second comment is now in a gap not covered by this change:

  • The first comment is captured by format_header
  • The third comment is captured by the updated else block in this change
  • But the second comment (covered by the old 'else' block) is now skipped

Will Look into this case

@matthewhughes934 I'm not sure your example currently works with the latest main: rustfmt 1.9.0-nightly (bdab101 2026-03-20)

Running your example produces invalid code.

pub trait MyTrait /* first */ trait /* second */ MyTrait /* third */ {}

@ytmimi
Copy link
Copy Markdown
Contributor Author

ytmimi commented Mar 24, 2026

I think comments between the literal trait and the identity will now be removed:

$ echo 'pub /* first */ trait /* second */ MyTrait /* third */ {}' | cargo run --bin rustfmt --quiet
pub /* first */ trait MyTrait /* third */ {}

Because that second comment is now in a gap not covered by this change:

  • The first comment is captured by format_header
  • The third comment is captured by the updated else block in this change
  • But the second comment (covered by the old 'else' block) is now skipped

Will Look into this case

@matthewhughes934 I'm not sure your example currently works with the latest main: rustfmt 1.9.0-nightly (bdab101 2026-03-20)

Running your example produces invalid code.

pub trait MyTrait /* first */ trait /* second */ MyTrait /* third */ {}

Okay, the latest commit addresses this case.

Comment on lines +1190 to +1198
result = combine_strs_with_missing_comments(
context,
&result,
&generics_str,
comment_span,
// infinite_width so we don't put the `ident + generics_str` on the next line
shape.infinite_width(),
true,
)?;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In the future I might want to make the headers mechanism to also do this part of the job too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was hoping to add the generics_str code to the HeaderPart vec as well to simplify things, but because format_header always uses shape.infinite_width() generic's weren't getting properly wrapped.

Maybe updating HeaderPart to something like this in the future is something we could consider:

pub(crate) struct HeaderPart<'a> {
    kind: HeaderPartKind<'a>,
    span: Span,
}

#[derive(Debug)]
enum HeaderPartKind<'a> {
    /// snippet of this part without surrounding space
    Snippet(Cow<'a, str>),
    /// A closure that will be used to dynamically compute the header part
    Computed(fn (&RewriteContext<'_>, Shape) -> RewriteResult)
}

Then we could modify format_header to dynamically compute any HeaderPartKind::Computed header parts with an appropriate shape offset.

Comment on lines +125 to +126
pub /* first */ trait /* second */ MyTrait /* third */ {}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please also add a test for something like this:

pub
   /* a */
  trait
    /* b */
      Foo
        /* c */
{}

Just to ensure that we don't accidentally format the comments (aligning them etc?).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So I think after my own testing and looking at the original issue, it looks like pub /* a */ trait had the issue, but it looks like latest rustfmt also chokes on this one:

trait /* a */ X {}

which gets reformatted to

trait X /* a */ X {}

but in here, it gets re-aligned (thus formatted):

  trait
/* b */
Foo

My recollection of the discussion at #5847 (comment) is that we don't want to introduce comment formatting for now.

I'm obviously for us being bold and all, and I disagree with postponing these things because I doubt it will get done in a reasonable time. I also don't think rustfmt breakages should be done using "editions", it should just be through a normal upgrade process (remind me some months later to look into whether rustup can pin rustfmt versions, whether via rust-toolchain.toml or something else).

But I just wanted to let you know in case you wanted to be consistent with the original decision (which applied here would require you to preserve the original comment formatting), and note that trait Foo /* a */ {} would be fine because that behavior is pre-existing.

Otherwise, feel free to r=me.

@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Mar 27, 2026

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@fee1-dead fee1-dead added S-waiting-on-author Status: awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: awaiting review from the assignee but also interested parties. labels Mar 29, 2026
@fee1-dead
Copy link
Copy Markdown
Member

fee1-dead commented Mar 29, 2026

Marking as waiting on author per latest comment :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: awaiting some action (such as code changes or more information) from the author.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Comment in trait visibility position causes token duplication Rustfmt is … confused 😳 by // comment after pub and before trait

4 participants