Skip to content

wrap_comments splits inside a link destination containing @ or /, breaking the link #7177

Open

Description

@DXist

When a markdown link is too long to fit on a line of its own, wrap_comments breaks
inside the link destination if that destination contains a @ or a /. The result is
a different document: links that resolved before formatting are unresolved after it.

rustfmt already handles this correctly in two cases — it leaves the line over
comment_width rather than break the link — but which case you get depends on whether
the destination happens to contain a character rustfmt treats as a break opportunity.

Version

rustfmt 1.11.0-nightly (6f5da406b5 2026-10-10)

Also reproduced on rustfmt 1.9.0-nightly (c397dae808 2026-07-02).

Not applicable to stable: wrap_comments is nightly-only, so stable warns
can't set `wrap_comments = true` and leaves the file untouched.

Configuration

edition = "2024"
unstable_features = true
wrap_comments = true

comment_width and max_width are the defaults, 80 and 100.

Reproduction

repro.rs:

pub fn queue_user_requested_email_otp() {}
pub fn queue_user_requested_email_otp_abc() {}
pub struct QueueUserRequestedEmailOtp;

pub mod email {
    /// A. [`queue_user_requested_email_otp`](fn@super::queue_user_requested_email_otp), over the channel.
    pub fn a() {}

    /// B. [`queue_user_requested_email_otp`](struct@super::QueueUserRequestedEmailOtp), over the channel.
    pub fn b() {}

    /// C. [`queue_user_requested_email_otp`](super::queue_user_requested_email_otp_abc), over the channel.
    pub fn c() {}

    /// D. [`queue_user_requested_email_otp`](some/path/queue_user_requested_email_otp.html), over it.
    pub fn d() {}

    /// E. [`queue_user_requested_email_otp`](https://example.com/queue_user_requested_email), over it.
    pub fn e() {}
}

Every link resolves as written:

$ rustdoc --edition 2024 -D rustdoc::broken_intra_doc_links repro.rs
$ echo $?
0

After rustfmt --edition 2024 repro.rs, it does not:

$ rustdoc --edition 2024 -D rustdoc::broken_intra_doc_links repro.rs
error: unresolved link to `queue_user_requested_email_otp`
 --> repro.rs:6:14
  |
6 |     /// A. [`queue_user_requested_email_otp`](fn@
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `queue_user_requested_email_otp` in scope
...
error: aborting due to 3 previous errors
$ echo $?
1

Actual

A and B break immediately after the disambiguator's @, and D after a /:

    /// A. [`queue_user_requested_email_otp`](fn@
    /// super::queue_user_requested_email_otp), over the channel.

    /// B. [`queue_user_requested_email_otp`](struct@
    /// super::QueueUserRequestedEmailOtp), over the channel.

    /// D. [`queue_user_requested_email_otp`](some/path/
    /// queue_user_requested_email_otp.html), over it.

C and E are left untouched, at 107 and 103 columns respectively, and both still
resolve.

Expected

All five behave as C and E do: the destination is never split, and the line is allowed
to exceed comment_width when there is nowhere legal to break.

Why the five differ

The destinations that survive are the ones rustfmt finds no break opportunity in. A
:: path has none, so C survives incidentally rather than by rule; E survives by the
explicit URL rule. @ and / are break opportunities, so A, B and D are split.

The protection therefore keys on the URL scheme rather than on being inside ](…).
Every rustdoc disambiguator is affected — fn@, struct@, mod@, macro@, value@,
type@, prim@ — as is any relative path.

Relation to earlier issues

Impact

A project that runs both checks gets cargo fmt and cargo doc contradicting each
other on the same toolchain: the link on one line fails fmt --check, and rustfmt's
wrapping of it fails the doc check. For an inline link with a disambiguator there is no
spelling that satisfies both.

Workaround

A reference-style definition, which #5096 keeps whole:

/// The twin of [`the email operation`], over the other channel.
///
/// [`the email operation`]: fn@super::queue_user_requested_email_otp

Activity

  1. added
    needs-triageThis issue or PR needs triaging to determine its status. Remove label once sufficiently triaged.
    on Oct 12, 2026
  2. jieyouxu commented on Oct 12, 2026

    @jieyouxu
    Member

    The @ is rustdoc syntax, indeed we can't break on the disambiguators. cf. https://doc.rust-lang.org/rustdoc/write-documentation/linking-to-items-by-name.html. NB if someone wants to look at this:

    • May need to consider (absolute and relative) paths containing @, but in that case, should likely follow rustdoc intra-doc link constraints and be conservative.
    • Consider other rustdoc intra-doc link syntax as well.

    I suspect the most robust scheme is the most conservative one, i.e. do not break for the links (URIs, relative or absolute paths, or rustdoc intra-doc links).

  3. added
    C-bugCategory: this is a bug; use also I-* labels for specific bug kinds, e.g. I-non-idempotency or I-ICE
    E-mediumCall for participation: Medium difficulty. Experience needed to fix: Intermediate.
    A-markdownArea: markdown formatting in doc comments
    and removed
    needs-triageThis issue or PR needs triaging to determine its status. Remove label once sufficiently triaged.
    on Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-markdownArea: markdown formatting in doc commentsC-bugCategory: this is a bug; use also I-* labels for specific bug kinds, e.g. I-non-idempotency or I-ICEE-mediumCall for participation: Medium difficulty. Experience needed to fix: Intermediate.UO-wrap_commentsUnstable option: wrap_comments

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions