From d9b90b0bff56c3547ff97a8e65b70f72e5cc0b11 Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas) Chirananthavat" Date: Fri, 12 Jun 2026 17:15:27 +0700 Subject: [PATCH 01/28] Rewrite safety requirements for `Allocator` impls I define the concept of "equivalent allocators", in order to be able to talk about cloning allocators, and to give commonsense guarantees about stdlib `Allocator` impls. I define the concept of "invalidating a memory block" in order to be able to talk about users not being allowed to use a block of allocated memory after its allocator is "gone". An `Allocator` implementation is now allowed to invalidate its allocations when the allocator is mutated or when a lifetime in the allocator type expires. Mutation of an `Allocator` should sensibly be allowed to invalidate its allocations. For example, the `bumpalo` crates has a `Bump::reset` method that takes `&mut self` and invalidates all past allocations. Accesses via `&` still must not invalidate past allocations since, for example, `Box` provides `&` access to the allocator. I still had the "allocator destructor" clause as a separate clause from the `&mut` clause, to avoid questions about whether drop glue of types that don't implement `Drop` but have fields that implement `Drop` counts as creating a `&mut` to the whole thing. The "lifetime expiry" clause closes a hole/ambiguity on when an allocator is considered to be "dropped" if it does not have a destructor. Additionally, this clause matches what is required for `Box::into_pin` and `{Rc, Arc}::pin` to be sound. (Those methods have an `A: 'static` bound to prevent allocating via a `&MyAllocator` and then running `MyAllocator`'s destructor.) --- library/core/src/alloc/mod.rs | 125 +++++++++++++++++++++++++--------- 1 file changed, 92 insertions(+), 33 deletions(-) diff --git a/library/core/src/alloc/mod.rs b/library/core/src/alloc/mod.rs index 102fb19efc8ea..0f99969513411 100644 --- a/library/core/src/alloc/mod.rs +++ b/library/core/src/alloc/mod.rs @@ -57,20 +57,78 @@ impl fmt::Display for AllocError { /// allocator does not support this (like jemalloc) or responds by returning a null pointer /// (such as `libc::malloc`), this must be caught by the implementation. /// +/// ### Equivalent allocators +/// +/// Multiple allocator values can sometimes be interchangeable with each other. +/// When this is the case, we refer to those allocators as being *equivalent* to +/// each other. +/// +/// The following conditions are sufficient conditions for allocators to be equivalent. +/// * An allocator is equivalent to itself. (Equivalence is reflexive.) +/// * If an allocator is equivalent to a second allocator, then +/// the second allocator is also equivalent to the first. (Equivalence is symmetric.) +/// * If an allocator is equivalent to a second allocator, and +/// the second allocator is equivalent to a third allocator, then +/// the first allocator is also equivalent to the third allocator. +/// (Equivalence is transitive.) +/// * Moving, subtyping, unsize-coercing, or trait-upcasting an allocator does not change +/// what the allocator is equivalent to. +/// * Copying or cloning allocator results in an allocator that's +/// equivalent to the initial allocator. +/// +/// Additionally, implementors of `Allocator` may specify additional equivalences +/// between allocators. It is the responsibility of such implementors to make sure +/// that equivalent allocators have "compatible" `Allocator` implementations. +/// In particular, the standard library specifies the following equivalences: +/// * A reference to an allocator (either `&` or `&mut`) is equivalent to +/// the allocator being referenced. +/// * A `Box`, `Rc`, or `Arc` containing an allocator is equivalent to +/// the allocator inside. +/// * All `Global` allocator instances are equivalent with each other. +/// * All `System` allocator instances are equivalent with each other. +/// +/// Note: Currently, the interaction between cloning and unsize-coercing allocators +/// is unsound, and there is ongoing discussion on how to revise the `Allocator` trait +/// to fix this. See [#156920]. +/// +/// [#156920]: https://github.com/rust-lang/rust/issues/156920 +/// /// ### Currently allocated memory /// -/// Some of the methods require that a memory block is *currently allocated* by an allocator. +/// Some of the methods require that a memory block is *currently allocated* by specific allocator. /// This means that: -/// * the starting address for that memory block was previously -/// returned by [`allocate`], [`grow`], or [`shrink`], and -/// * the memory block has not subsequently been deallocated. +/// * the starting address for that memory block was previously +/// returned by the [`allocate`], [`allocate_zeroed`], [`grow`], or [`shrink`] methods, +/// called on an allocator that's equivalent to this specific allocator; and +/// * the memory block has not subsequently been [*invalidated*]. +/// +/// [*invalidated*]: #invalidating-memory-blocks /// -/// A memory block is deallocated by a call to [`deallocate`], -/// or by a call to [`grow`] or [`shrink`] that returns `Ok`. -/// A call to `grow` or `shrink` that returns `Err`, -/// does not deallocate the memory block passed to it. +/// ### Invalidating memory blocks +/// +/// A memory block that is currently allocated becomes *invalidated* when one +/// of the following happens: +/// * The memory block is deallocated. This occurs when the memory block +/// is passed as an argument to a [`deallocate`] call, or when it is passed +/// as an argument to a [`grow`] or [`shrink`] call that returns `Ok`. +/// * All (equivalent) allocators that this memory block is allocated with, +/// each has one of the following happen to them: +/// * The allocator's destructor runs. +/// * The allocator is mutated through public API taking `&mut` access. +/// * One of the borrow-checker lifetimes in the allocator's type expires. +/// +/// Note that these conditions imply that a collection may ensure that +/// any specific currently allocated memory block won't be invalidated, by: +/// * not deallocating that memory block, +/// * owning an allocator that memory block is allocated with, and +/// * not publicly exposing `&mut` access to that allocator. +/// +/// Also note that accessing an allocator with `&` access cannot invalidate +/// its memory blocks. Therefore, collections may safely expose `&` access +/// to its allocator. /// /// [`allocate`]: Allocator::allocate +/// [`allocate_zeroed`]: Allocator::allocate_zeroed /// [`grow`]: Allocator::grow /// [`shrink`]: Allocator::shrink /// [`deallocate`]: Allocator::deallocate @@ -89,16 +147,12 @@ impl fmt::Display for AllocError { /// /// # Safety /// -/// Memory blocks that are [*currently allocated*] by an allocator, -/// must point to valid memory, and retain their validity until either: -/// - the memory block is deallocated, or -/// - the allocator is dropped. -/// -/// Copying, cloning, or moving the allocator must not invalidate memory blocks returned from it. -/// A copied or cloned allocator must behave like the original allocator. -/// -/// A memory block which is [*currently allocated*] may be passed to -/// any method of the allocator that accepts such an argument. +/// Implementors of `Allocator` must ensure that a memory block that +/// are [*currently allocated*] by the allocator points to valid memory, +/// until that memory block is [*invalidated*]. The implementor must also +/// not violate this invariant of `Allocator` via allocator equivalences +/// that are in the implementor's control (e.g., via a misbehaving +/// `impl Clone for Box`). /// /// Additionally, any memory block returned by the allocator must /// satisfy the allocation invariants described in `core::ptr`. @@ -107,7 +161,9 @@ impl fmt::Display for AllocError { /// /// This ensures that pointer arithmetic within the allocation /// (for example, `ptr.add(len)`) cannot overflow the address space. +/// /// [*currently allocated*]: #currently-allocated-memory +/// [*invalidated*]: #invalidating-memory-blocks #[unstable(feature = "allocator_api", issue = "32838")] #[rustc_const_unstable(feature = "const_heap", issue = "79597")] pub const unsafe trait Allocator { @@ -118,11 +174,13 @@ pub const unsafe trait Allocator { /// The returned block may have a larger size than specified by `layout.size()`, and may or may /// not have its contents initialized. /// - /// The returned block of memory remains valid as long as it is [*currently allocated*] and the shorter of: - /// - the borrow-checker lifetime of the allocator type itself. - /// - as long as the allocator and all its clones have not been dropped. + /// Note that the returned block of memory is considered [*currently allocated*] + /// with this allocator (and equivalent allocators). + /// Therefore, it is the responsibility of implementors of `Allocator` to make sure that + /// this block of memory points to valid memory until the block is [*invalidated*] /// /// [*currently allocated*]: #currently-allocated-memory + /// [*invalidated*]: #invalidating-memory-blocks /// /// # Errors /// @@ -178,13 +236,12 @@ pub const unsafe trait Allocator { /// memory. The pointer is suitable for holding data described by `new_layout`. To accomplish /// this, the allocator may extend the allocation referenced by `ptr` to fit the new layout. /// - /// If this returns `Ok`, then ownership of the memory block referenced by `ptr` has been - /// transferred to this allocator. Any access to the old `ptr` is Undefined Behavior, even if the - /// allocation was grown in-place. The newly returned pointer is the only valid pointer - /// for accessing this memory now. + /// If this returns `Ok`, then the memory block referenced by `ptr` has been [*invalidated*]. + /// Any access to the old `ptr` is Undefined Behavior, even if the allocation was grown in-place. + /// The newly returned pointer is the only valid pointer for accessing this memory now. /// - /// If this method returns `Err`, then ownership of the memory block has not been transferred to - /// this allocator, and the contents of the memory block are unaltered. + /// If this method returns `Err`, then the memory block has not been *invalidated*, + /// and the contents of the memory block are unaltered. /// /// # Safety /// @@ -196,6 +253,7 @@ pub const unsafe trait Allocator { /// /// [*currently allocated*]: #currently-allocated-memory /// [*fit*]: #memory-fitting + /// [*invalidated*]: #invalidating-memory-blocks /// /// # Errors /// @@ -305,13 +363,13 @@ pub const unsafe trait Allocator { /// memory. The pointer is suitable for holding data described by `new_layout`. To accomplish /// this, the allocator may shrink the allocation referenced by `ptr` to fit the new layout. /// - /// If this returns `Ok`, then ownership of the memory block referenced by `ptr` has been - /// transferred to this allocator. Any access to the old `ptr` is Undefined Behavior, even if the - /// allocation was shrunk in-place. The newly returned pointer is the only valid pointer - /// for accessing this memory now. /// - /// If this method returns `Err`, then ownership of the memory block has not been transferred to - /// this allocator, and the contents of the memory block are unaltered. + /// If this returns `Ok`, then the memory block referenced by `ptr` has been [*invalidated*]. + /// Any access to the old `ptr` is Undefined Behavior, even if the allocation was shrunk in-place. + /// The newly returned pointer is the only valid pointer for accessing this memory now. + /// + /// If this method returns `Err`, then the memory block has not been *invalidated*, + /// and the contents of the memory block are unaltered. /// /// # Safety /// @@ -323,6 +381,7 @@ pub const unsafe trait Allocator { /// /// [*currently allocated*]: #currently-allocated-memory /// [*fit*]: #memory-fitting + /// [*invalidated*]: #invalidating-memory-blocks /// /// # Errors /// From e9929d760f6bbfee1bfcc3488b694b34896d7693 Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas) Chirananthavat" Date: Mon, 15 Jun 2026 17:00:31 +0700 Subject: [PATCH 02/28] Address feedback comments. --- library/core/src/alloc/mod.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/library/core/src/alloc/mod.rs b/library/core/src/alloc/mod.rs index 0f99969513411..ec2f106e79364 100644 --- a/library/core/src/alloc/mod.rs +++ b/library/core/src/alloc/mod.rs @@ -95,10 +95,10 @@ impl fmt::Display for AllocError { /// /// ### Currently allocated memory /// -/// Some of the methods require that a memory block is *currently allocated* by specific allocator. +/// Some of the methods require that a memory block is *currently allocated* by some specific allocator. /// This means that: -/// * the starting address for that memory block was previously -/// returned by the [`allocate`], [`allocate_zeroed`], [`grow`], or [`shrink`] methods, +/// * the starting address for that memory block was previously returned by +/// the [`allocate`], [`allocate_zeroed`], [`grow`], [`grow_zeroed`], or [`shrink`] methods, /// called on an allocator that's equivalent to this specific allocator; and /// * the memory block has not subsequently been [*invalidated*]. /// @@ -110,7 +110,7 @@ impl fmt::Display for AllocError { /// of the following happens: /// * The memory block is deallocated. This occurs when the memory block /// is passed as an argument to a [`deallocate`] call, or when it is passed -/// as an argument to a [`grow`] or [`shrink`] call that returns `Ok`. +/// as an argument to a [`grow`], [`grow_zeroed`] or [`shrink`] call that returns `Ok`. /// * All (equivalent) allocators that this memory block is allocated with, /// each has one of the following happen to them: /// * The allocator's destructor runs. @@ -127,9 +127,19 @@ impl fmt::Display for AllocError { /// its memory blocks. Therefore, collections may safely expose `&` access /// to its allocator. /// +/// Also note that, even in cases where are other "alive" allocators known to be +/// equivalent to a given collection's allocator, most collections still should +/// not publicly expose `&mut` access to its allocator. The fact that there are +/// other "alive" allocators would prevent this `&mut` access from invalidating +/// the collection's memory block, but public `&mut` access is still likely to +/// be unsound, since a user could replace the collection's allocator with +/// a non-equivalent allocator, causing the collection to deallocate its memory +/// with the wrong allocator. +/// /// [`allocate`]: Allocator::allocate /// [`allocate_zeroed`]: Allocator::allocate_zeroed /// [`grow`]: Allocator::grow +/// [`grow_zeroed`]: Allocator::grow_zeroed /// [`shrink`]: Allocator::shrink /// [`deallocate`]: Allocator::deallocate /// @@ -140,7 +150,8 @@ impl fmt::Display for AllocError { /// * the memory block must be *currently allocated* with alignment of [`layout.align()`], and /// * [`layout.size()`] must fall in the range `min ..= max`, where: /// - `min` is the size of the layout used to allocate the block, and -/// - `max` is the actual size returned from [`allocate`], [`grow`], or [`shrink`]. +/// - `max` is the actual size returned from [`allocate`], [`allocate_zeroed`], +/// [`grow`], [`grow_zeroed`], or [`shrink`]. /// /// [`layout.align()`]: Layout::align /// [`layout.size()`]: Layout::size @@ -148,7 +159,7 @@ impl fmt::Display for AllocError { /// # Safety /// /// Implementors of `Allocator` must ensure that a memory block that -/// are [*currently allocated*] by the allocator points to valid memory, +/// is [*currently allocated*] by the allocator points to valid memory, /// until that memory block is [*invalidated*]. The implementor must also /// not violate this invariant of `Allocator` via allocator equivalences /// that are in the implementor's control (e.g., via a misbehaving @@ -237,7 +248,7 @@ pub const unsafe trait Allocator { /// this, the allocator may extend the allocation referenced by `ptr` to fit the new layout. /// /// If this returns `Ok`, then the memory block referenced by `ptr` has been [*invalidated*]. - /// Any access to the old `ptr` is Undefined Behavior, even if the allocation was grown in-place. + /// The old `ptr` must not be used to access the memory, even if the allocation was grown in-place. /// The newly returned pointer is the only valid pointer for accessing this memory now. /// /// If this method returns `Err`, then the memory block has not been *invalidated*, @@ -365,7 +376,7 @@ pub const unsafe trait Allocator { /// /// /// If this returns `Ok`, then the memory block referenced by `ptr` has been [*invalidated*]. - /// Any access to the old `ptr` is Undefined Behavior, even if the allocation was shrunk in-place. + /// The old `ptr` must not be used to access the memory, even if the allocation was shrunk in-place. /// The newly returned pointer is the only valid pointer for accessing this memory now. /// /// If this method returns `Err`, then the memory block has not been *invalidated*, From 84f289674c391d8a049373a3c1ea46759be2cdaf Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 6 Jun 2026 22:19:12 -0700 Subject: [PATCH 03/28] rustdoc: do not include extra stuff in span This change prevents our lints from returning a span with stuff in it that isn't actually part of the doc comment. When that happens, it returns `None` instead. --- compiler/rustc_resolve/src/rustdoc.rs | 67 ++++++++++++------- .../lints/invalid-html-tags-ice-146890.rs | 8 +-- .../lints/invalid-html-tags-ice-146890.stderr | 2 - tests/rustdoc-ui/lints/invalid-html-tags.rs | 2 +- .../rustdoc-ui/lints/invalid-html-tags.stderr | 1 - .../lints/redundant_explicit_links_split.rs | 43 ++++++++++++ .../redundant_explicit_links_split.stderr | 45 +++++++++++++ 7 files changed, 137 insertions(+), 31 deletions(-) create mode 100644 tests/rustdoc-ui/lints/redundant_explicit_links_split.rs create mode 100644 tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 92bc577f59201..fdfe5afb7d7ec 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use std::mem; use std::ops::Range; @@ -641,41 +642,61 @@ pub fn source_span_for_markdown_range_inner( let mut start_bytes = 0; let mut end_bytes = 0; + let span = span_of_fragments(fragments)?; + + let mut line_bytes = 0; + let mut sorted_fragments = fragments.to_vec(); + sorted_fragments.sort_by_key(|fragment| fragment.span.lo().0); 'outer: for (line_no, md_line) in md_lines.enumerate() { loop { let source_line = src_lines.next()?; - match source_line.find(md_line) { - Some(offset) => { - if line_no == starting_line { - start_bytes += offset; + // Since we're counting bytes, `source_line_len` includes the "\n". + let source_line_len = u32::try_from(source_line.len() + 1).unwrap(); + let has_fragment = sorted_fragments + .binary_search_by(|fragment| { + if fragment.span.hi().0 < span.lo().0 + line_bytes { + Ordering::Less + } else if fragment.span.lo().0 > span.lo().0 + line_bytes + source_line_len { + Ordering::Greater + } else { + Ordering::Equal + } + }) + .is_ok(); + line_bytes += source_line_len; + if has_fragment && let Some(offset) = source_line.find(md_line) { + if line_no == starting_line { + start_bytes += offset; - if starting_line == ending_line { - break 'outer; - } - } else if line_no == ending_line { - end_bytes += offset; + if starting_line == ending_line { break 'outer; - } else if line_no < starting_line { - start_bytes += source_line.len() - md_line.len(); - } else { - end_bytes += source_line.len() - md_line.len(); } - break; + } else if line_no == ending_line { + end_bytes += offset; + break 'outer; + } else if line_no < starting_line { + start_bytes += source_line.len() - md_line.len(); + } else { + end_bytes += source_line.len() - md_line.len(); } - None => { - // Since this is a source line that doesn't include a markdown line, - // we have to count the newline that we split from earlier. - if line_no <= starting_line { - start_bytes += source_line.len() + 1; - } else { - end_bytes += source_line.len() + 1; - } + break; + } else { + // Since this is a source line that doesn't include a markdown line, + // we have to count the newline that we split from earlier. + if line_no <= starting_line { + start_bytes += source_line.len() + 1; + } else if source_line.chars().any(|c| !c.is_whitespace()) { + // If we're past the first line, but haven't found the last line, + // we can only return a contiguous span if every line is either + // part of the doc comment or blank. + return None; + } else { + end_bytes += source_line.len() + 1; } } } } - let span = span_of_fragments(fragments)?; let src_span = span.from_inner(InnerSpan::new( md_range.start + start_bytes, md_range.end + start_bytes + end_bytes, diff --git a/tests/rustdoc-ui/lints/invalid-html-tags-ice-146890.rs b/tests/rustdoc-ui/lints/invalid-html-tags-ice-146890.rs index d7efc201e7e39..53df3e9eb5a80 100644 --- a/tests/rustdoc-ui/lints/invalid-html-tags-ice-146890.rs +++ b/tests/rustdoc-ui/lints/invalid-html-tags-ice-146890.rs @@ -7,17 +7,17 @@ /// /// key +//~^^ ERROR: unclosed HTML tag `TH` /// +//~^^ ERROR: unopened HTML tag `TD` /// value +//~^^ ERROR: unclosed HTML tag `TH` /// +//~^^ ERROR: unopened HTML tag `TD` /// /// | |_____^ | @@ -18,7 +17,6 @@ error: unopened HTML tag `TD` | LL | /// | |_____^ diff --git a/tests/rustdoc-ui/lints/invalid-html-tags.rs b/tests/rustdoc-ui/lints/invalid-html-tags.rs index 8003e5efdd582..f5700e5846e51 100644 --- a/tests/rustdoc-ui/lints/invalid-html-tags.rs +++ b/tests/rustdoc-ui/lints/invalid-html-tags.rs @@ -164,8 +164,8 @@ pub fn r() {} /// >
/// > href="#broken" +//~^^ ERROR incomplete HTML tag `img` pub fn s() {} ///
diff --git a/tests/rustdoc-ui/lints/invalid-html-tags.stderr b/tests/rustdoc-ui/lints/invalid-html-tags.stderr index b6ec22c247901..3256004ddb6ab 100644 --- a/tests/rustdoc-ui/lints/invalid-html-tags.stderr +++ b/tests/rustdoc-ui/lints/invalid-html-tags.stderr @@ -123,7 +123,6 @@ error: incomplete HTML tag `img` | LL | /// > href="#broken" | |____________________^ diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs b/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs new file mode 100644 index 0000000000000..9de8bbe3b8c98 --- /dev/null +++ b/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs @@ -0,0 +1,43 @@ +#![deny(rustdoc::redundant_explicit_links)] + +// Right now, redundant_explicit_links won't produce a warning at all if +// rustdoc isn't able to calculate an accurate span for the link. +// +// If that changes and this test starts to fail, you should add the +// appropriate annotations, and, also, make sure that it doesn't +// suggest a correction that wipes out the `fn` formal signature +// or the `#[inline]` attribute. + +/// [std::clone::Clone]( +pub fn split_outer_inner() { + //! std::clone::Clone) +} + +/// [std::clone::Clone](std::clone::Clone +pub fn split_outer_inner_b() { + //! ) +} + +/// [std::clone::Clone]( +#[inline] +/// std::clone::Clone) +pub fn split_attr() { +} + +/// [std::clone::Clone](std::clone::Clone +#[inline] +/// ) +pub fn split_attr_b() { +} + +/// [std::clone::Clone]( +/// std::clone::Clone) +//~^^ ERROR redundant_explicit_links +pub fn not_split() { +} + +/// [std::clone::Clone](std::clone::Clone +/// ) +//~^^ ERROR redundant_explicit_links +pub fn not_split_b() { +} diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr b/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr new file mode 100644 index 0000000000000..3e2e99f10d9f7 --- /dev/null +++ b/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr @@ -0,0 +1,45 @@ +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:33:25 + | +LL | /// [std::clone::Clone]( + | ______-----------------__^ + | | | + | | because label contains path that resolves to same destination +LL | | /// std::clone::Clone) + | |_____________________^ explicit target is redundant + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links +note: the lint level is defined here + --> $DIR/redundant_explicit_links_split.rs:1:9 + | +LL | #![deny(rustdoc::redundant_explicit_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: remove explicit link target + | +LL - /// [std::clone::Clone]( +LL - /// std::clone::Clone) +LL + /// [std::clone::Clone] + | + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:39:25 + | +LL | /// [std::clone::Clone](std::clone::Clone + | ______-----------------__^ + | | | + | | because label contains path that resolves to same destination +LL | | /// ) + | |____^ explicit target is redundant + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links +help: remove explicit link target + | +LL - /// [std::clone::Clone](std::clone::Clone +LL - /// ) +LL + /// [std::clone::Clone] + | + +error: aborting due to 2 previous errors + From 2001b88a962332f3799d3719867812900561d5bb Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sun, 7 Jun 2026 17:41:40 -0700 Subject: [PATCH 04/28] Fix `unbalanced_ticks` when doc isn't contiguous --- src/tools/clippy/clippy_lints/src/doc/mod.rs | 13 +++---------- .../clippy/tests/ui/doc/unbalanced_ticks.stderr | 5 ++--- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/doc/mod.rs b/src/tools/clippy/clippy_lints/src/doc/mod.rs index c5816cb8b2d63..013fb32bdaf3c 100644 --- a/src/tools/clippy/clippy_lints/src/doc/mod.rs +++ b/src/tools/clippy/clippy_lints/src/doc/mod.rs @@ -1242,7 +1242,8 @@ fn check_doc<'a, Events: Iterator, Range, Range tests/ui/doc/unbalanced_ticks.rs:6:5 + --> tests/ui/doc/unbalanced_ticks.rs:6:1 | -LL | /// This is a doc comment with `unbalanced_tick marks and several words that - | _____^ +LL | / /// This is a doc comment with `unbalanced_tick marks and several words that LL | | LL | | /// should be `encompassed_by` tick marks because they `contain_underscores`. LL | | /// Because of the initial `unbalanced_tick` pair, the error message is From e66a50496cf5e996bb354f61508db540d62eb51a Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sun, 7 Jun 2026 17:46:08 -0700 Subject: [PATCH 05/28] Add test case for split missing punctuation --- ...doc_paragraph_missing_punctuation_split_16169.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/tools/clippy/tests/ui/doc/doc_paragraph_missing_punctuation_split_16169.rs diff --git a/src/tools/clippy/tests/ui/doc/doc_paragraph_missing_punctuation_split_16169.rs b/src/tools/clippy/tests/ui/doc/doc_paragraph_missing_punctuation_split_16169.rs new file mode 100644 index 0000000000000..c01b0c90b120e --- /dev/null +++ b/src/tools/clippy/tests/ui/doc/doc_paragraph_missing_punctuation_split_16169.rs @@ -0,0 +1,13 @@ +//@ check-pass +// https://github.com/rust-lang/rust-clippy/issues/16169 +#![allow(clippy::mixed_attributes_style)] + +/// +pub fn dont_warn_inner_outer() { + //!w +} +/// +#[inline] +///w +pub fn dont_warn_split_by_attr() { +} From f7302b9723082a9310e901b58eae530363571d96 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 13 Jun 2026 09:47:29 -0700 Subject: [PATCH 06/28] Skip the unhelpful binary search It requires an allocation, and doesn't seem to help in practice. Fixes a nit found during review. --- compiler/rustc_resolve/src/rustdoc.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index fdfe5afb7d7ec..8efad0a343ac5 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -1,4 +1,3 @@ -use std::cmp::Ordering; use std::mem; use std::ops::Range; @@ -645,24 +644,15 @@ pub fn source_span_for_markdown_range_inner( let span = span_of_fragments(fragments)?; let mut line_bytes = 0; - let mut sorted_fragments = fragments.to_vec(); - sorted_fragments.sort_by_key(|fragment| fragment.span.lo().0); 'outer: for (line_no, md_line) in md_lines.enumerate() { loop { let source_line = src_lines.next()?; // Since we're counting bytes, `source_line_len` includes the "\n". let source_line_len = u32::try_from(source_line.len() + 1).unwrap(); - let has_fragment = sorted_fragments - .binary_search_by(|fragment| { - if fragment.span.hi().0 < span.lo().0 + line_bytes { - Ordering::Less - } else if fragment.span.lo().0 > span.lo().0 + line_bytes + source_line_len { - Ordering::Greater - } else { - Ordering::Equal - } - }) - .is_ok(); + let has_fragment = fragments.iter().any(|fragment| { + fragment.span.hi().0 >= span.lo().0 + line_bytes + && fragment.span.lo().0 <= span.lo().0 + line_bytes + source_line_len + }); line_bytes += source_line_len; if has_fragment && let Some(offset) = source_line.find(md_line) { if line_no == starting_line { From b66a23fb4b2f9cff20ea6f8b34d223e4073a99d8 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 17 Jun 2026 17:58:10 -0700 Subject: [PATCH 07/28] Rename `span` and `line_bytes` to more specific Co-authored-by: binarycat --- compiler/rustc_resolve/src/rustdoc.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 8efad0a343ac5..9abb79d5ca02f 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -641,19 +641,20 @@ pub fn source_span_for_markdown_range_inner( let mut start_bytes = 0; let mut end_bytes = 0; - let span = span_of_fragments(fragments)?; + let span_of_all_fragments = span_of_fragments(fragments)?; - let mut line_bytes = 0; + let mut prev_lines_bytes = 0; 'outer: for (line_no, md_line) in md_lines.enumerate() { loop { let source_line = src_lines.next()?; // Since we're counting bytes, `source_line_len` includes the "\n". let source_line_len = u32::try_from(source_line.len() + 1).unwrap(); let has_fragment = fragments.iter().any(|fragment| { - fragment.span.hi().0 >= span.lo().0 + line_bytes - && fragment.span.lo().0 <= span.lo().0 + line_bytes + source_line_len + fragment.span.hi().0 >= span_of_all_fragments.lo().0 + prev_lines_bytes + && fragment.span.lo().0 + <= span_of_all_fragments.lo().0 + prev_lines_bytes + source_line_len }); - line_bytes += source_line_len; + prev_lines_bytes += source_line_len; if has_fragment && let Some(offset) = source_line.find(md_line) { if line_no == starting_line { start_bytes += offset; @@ -687,7 +688,7 @@ pub fn source_span_for_markdown_range_inner( } } - let src_span = span.from_inner(InnerSpan::new( + let src_span = span_of_all_fragments.from_inner(InnerSpan::new( md_range.start + start_bytes, md_range.end + start_bytes + end_bytes, )); From f93f2e446b457c871510340c22a4a68192ae48d1 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 17 Jun 2026 18:07:20 -0700 Subject: [PATCH 08/28] Use an intermediate variable for readability Co-authored-by: binarycat --- compiler/rustc_resolve/src/rustdoc.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 9abb79d5ca02f..4cae23ed7e12a 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -641,20 +641,20 @@ pub fn source_span_for_markdown_range_inner( let mut start_bytes = 0; let mut end_bytes = 0; - let span_of_all_fragments = span_of_fragments(fragments)?; + let span_of_all_fragments: Span = span_of_fragments(fragments)?; let mut prev_lines_bytes = 0; 'outer: for (line_no, md_line) in md_lines.enumerate() { loop { let source_line = src_lines.next()?; - // Since we're counting bytes, `source_line_len` includes the "\n". - let source_line_len = u32::try_from(source_line.len() + 1).unwrap(); - let has_fragment = fragments.iter().any(|fragment| { - fragment.span.hi().0 >= span_of_all_fragments.lo().0 + prev_lines_bytes - && fragment.span.lo().0 - <= span_of_all_fragments.lo().0 + prev_lines_bytes + source_line_len - }); - prev_lines_bytes += source_line_len; + let source_line_len = u32::try_from(source_line.len()).unwrap(); + let source_line_span = + span_of_all_fragments.split_at(prev_lines_bytes).1.split_at(source_line_len).0; + let has_fragment = fragments + .iter() + .any(|fragment| fragment.span.contains(source_line_span.shrink_to_hi())); + // Since we're counting bytes, `prev_line_bytes` includes the "\n". + prev_lines_bytes += source_line_len + 1; if has_fragment && let Some(offset) = source_line.find(md_line) { if line_no == starting_line { start_bytes += offset; From 7975dc779706cb384101867ffa9008816ed01d7e Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 17 Jun 2026 19:19:02 -0700 Subject: [PATCH 09/28] Add clarifying comment Co-authored-by: binarycat --- compiler/rustc_resolve/src/rustdoc.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 4cae23ed7e12a..42a5005552825 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -673,13 +673,14 @@ pub fn source_span_for_markdown_range_inner( break; } else { // Since this is a source line that doesn't include a markdown line, - // we have to count the newline that we split from earlier. + // we have to count it and its newline as non-markdown bytes. if line_no <= starting_line { start_bytes += source_line.len() + 1; } else if source_line.chars().any(|c| !c.is_whitespace()) { - // If we're past the first line, but haven't found the last line, - // we can only return a contiguous span if every line is either - // part of the doc comment or blank. + // We're past the first line, but haven't found the last line, + // but we found a non-empty non-markdown line. + // This could be an attribute, and we don't want a diagnostic + // suggesting to delete that attribute, so we return None to be safe. return None; } else { end_bytes += source_line.len() + 1; From 4d83004499d40452cef97b9cd103f53cb98fccd2 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 17 Jun 2026 19:34:27 -0700 Subject: [PATCH 10/28] Remove unused `Option` return --- src/librustdoc/passes/lint/redundant_explicit_links.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs index 8da21f100c6a3..8ccf819fff987 100644 --- a/src/librustdoc/passes/lint/redundant_explicit_links.rs +++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs @@ -80,7 +80,7 @@ fn check_redundant_explicit_link<'md>( hir_id: HirId, doc: &'md str, resolutions: &DocLinkResMap, -) -> Option<()> { +) { let mut broken_line_callback = |link: BrokenLink<'md>| Some((link.reference, "".into())); let mut offset_iter = Parser::new_with_broken_link_callback( doc, @@ -146,8 +146,6 @@ fn check_redundant_explicit_link<'md>( } } } - - None } /// FIXME(ChAoSUnItY): Too many arguments. From 9438aac0c297bbc8804c0e6796c24637df1f6e99 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 17 Jun 2026 20:15:28 -0700 Subject: [PATCH 11/28] Handle span failure in redundant_explicit_links --- .../passes/lint/redundant_explicit_links.rs | 182 +++++++++++++----- .../lints/redundant_explicit_links_split.rs | 27 ++- .../redundant_explicit_links_split.stderr | 74 +++++-- 3 files changed, 205 insertions(+), 78 deletions(-) diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs index 8ccf819fff987..ad45b6e1d1c5b 100644 --- a/src/librustdoc/passes/lint/redundant_explicit_links.rs +++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs @@ -115,7 +115,7 @@ fn check_redundant_explicit_link<'md>( } if dest_url.ends_with(resolvable_link) || resolvable_link.ends_with(&*dest_url) { - match link_type { + let check_result = match link_type { LinkType::Inline | LinkType::ReferenceUnknown => { check_inline_or_reference_unknown_redundancy( cx, @@ -127,27 +127,54 @@ fn check_redundant_explicit_link<'md>( dest_url.to_string(), link_data, if link_type == LinkType::Inline { (b'(', b')') } else { (b'[', b']') }, - ); + ) } - LinkType::Reference => { - check_reference_redundancy( - cx, - item, - hir_id, - doc, - resolutions, - link_range, - &dest_url, - link_data, - ); - } - _ => {} + LinkType::Reference => check_reference_redundancy( + cx, + item, + hir_id, + doc, + resolutions, + link_range, + &dest_url, + link_data, + ), + _ => Ok(()), + }; + if let Err(lint) = check_result { + cx.tcx.emit_node_span_lint( + crate::lint::REDUNDANT_EXPLICIT_LINKS, + hir_id, + item.attr_span(cx.tcx), + lint, + ); } } } } } +struct RedundantExplicitLinksCannotSuggest { + attr_span: Span, + display_link: String, + dest_link: String, +} + +impl<'a> Diagnostic<'a, ()> for RedundantExplicitLinksCannotSuggest { + fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { + let Self { attr_span, display_link, dest_link } = self; + + Diag::new(dcx, level, "redundant explicit link target") + .with_span_label( + attr_span, + format!("explicit target `{dest_link}` is redundant because label `{display_link}` resolves to same destination") + ) + .with_note( + "when a link's destination is not specified,\nthe label is used to resolve intra-doc links" + ) + } +} + /// FIXME(ChAoSUnItY): Too many arguments. fn check_inline_or_reference_unknown_redundancy( cx: &DocContext<'_>, @@ -159,7 +186,7 @@ fn check_inline_or_reference_unknown_redundancy( dest: String, link_data: LinkData, (open, close): (u8, u8), -) -> Option<()> { +) -> Result<(), RedundantExplicitLinksCannotSuggest> { struct RedundantExplicitLinks { explicit_span: Span, display_span: Span, @@ -194,42 +221,65 @@ fn check_inline_or_reference_unknown_redundancy( } } - let (resolvable_link, resolvable_link_range) = - (&link_data.resolvable_link?, &link_data.resolvable_link_range?); - let (dest_res, display_res) = - (find_resolution(resolutions, &dest)?, find_resolution(resolutions, resolvable_link)?); + let (Some(resolvable_link), Some(resolvable_link_range)) = + (&link_data.resolvable_link, &link_data.resolvable_link_range) + else { + return Ok(()); + }; + let (Some(dest_res), Some(display_res)) = + (find_resolution(resolutions, &dest), find_resolution(resolutions, resolvable_link)) + else { + return Ok(()); + }; if dest_res == display_res { + let attr_span = item.attr_span(cx.tcx); let link_span = match source_span_for_markdown_range(cx.tcx, doc, &link_range, &item.attrs.doc_strings) { Some((sp, from_expansion)) => { if from_expansion { - return None; + return Ok(()); } sp } - None => item.attr_span(cx.tcx), + None => attr_span, }; - let (explicit_span, false) = source_span_for_markdown_range( + let explicit_span = match source_span_for_markdown_range( cx.tcx, doc, &offset_explicit_range(doc, link_range, open, close), &item.attrs.doc_strings, - )? - else { + ) { + Some((explicit_span, false)) => explicit_span, // This `span` comes from macro expansion so skipping it. - return None; + Some((_, true)) => return Ok(()), + // Cannot give a contiguous span for this link. + None => { + return Err(RedundantExplicitLinksCannotSuggest { + display_link: resolvable_link.clone(), + dest_link: dest.to_string(), + attr_span, + }); + } }; - let (display_span, false) = source_span_for_markdown_range( + let display_span = match source_span_for_markdown_range( cx.tcx, doc, resolvable_link_range, &item.attrs.doc_strings, - )? - else { + ) { + Some((display_span, false)) => display_span, // This `span` comes from macro expansion so skipping it. - return None; + Some((_, true)) => return Ok(()), + // Cannot give a contiguous span for this link. + None => { + return Err(RedundantExplicitLinksCannotSuggest { + display_link: resolvable_link.clone(), + dest_link: dest.to_string(), + attr_span, + }); + } }; cx.tcx.emit_node_span_lint( @@ -245,7 +295,7 @@ fn check_inline_or_reference_unknown_redundancy( ); } - None + Ok(()) } /// FIXME(ChAoSUnItY): Too many arguments. @@ -258,7 +308,7 @@ fn check_reference_redundancy( link_range: Range, dest: &CowStr<'_>, link_data: LinkData, -) -> Option<()> { +) -> Result<(), RedundantExplicitLinksCannotSuggest> { struct RedundantExplicitLinkTarget { explicit_span: Span, display_span: Span, @@ -292,49 +342,83 @@ fn check_reference_redundancy( } } - let (resolvable_link, resolvable_link_range) = - (&link_data.resolvable_link?, &link_data.resolvable_link_range?); - let (dest_res, display_res) = - (find_resolution(resolutions, dest)?, find_resolution(resolutions, resolvable_link)?); + let (Some(resolvable_link), Some(resolvable_link_range)) = + (&link_data.resolvable_link, &link_data.resolvable_link_range) + else { + return Ok(()); + }; + let (Some(dest_res), Some(display_res)) = + (find_resolution(resolutions, dest), find_resolution(resolutions, resolvable_link)) + else { + return Ok(()); + }; if dest_res == display_res { + let attr_span = item.attr_span(cx.tcx); let link_span = match source_span_for_markdown_range(cx.tcx, doc, &link_range, &item.attrs.doc_strings) { Some((sp, from_expansion)) => { if from_expansion { - return None; + // This `span` comes from macro expansion so skipping it. + return Ok(()); } sp } - None => item.attr_span(cx.tcx), + None => attr_span, }; - let (explicit_span, false) = source_span_for_markdown_range( + let explicit_span = match source_span_for_markdown_range( cx.tcx, doc, &offset_explicit_range(doc, link_range.clone(), b'[', b']'), &item.attrs.doc_strings, - )? - else { + ) { + Some((explicit_span, false)) => explicit_span, // This `span` comes from macro expansion so skipping it. - return None; + Some((_, true)) => return Ok(()), + // Cannot give a contiguous span for this link. + None => { + return Err(RedundantExplicitLinksCannotSuggest { + display_link: resolvable_link.clone(), + dest_link: dest.to_string(), + attr_span, + }); + } }; - let (display_span, false) = source_span_for_markdown_range( + let display_span = match source_span_for_markdown_range( cx.tcx, doc, resolvable_link_range, &item.attrs.doc_strings, - )? - else { + ) { + Some((display_span, false)) => display_span, // This `span` comes from macro expansion so skipping it. - return None; + Some((_, true)) => return Ok(()), + // Cannot give a contiguous span for this link. + None => { + return Err(RedundantExplicitLinksCannotSuggest { + display_link: resolvable_link.clone(), + dest_link: dest.to_string(), + attr_span, + }); + } }; - let (def_span, _) = source_span_for_markdown_range( + let def_span = match source_span_for_markdown_range( cx.tcx, doc, &offset_reference_def_range(doc, dest, link_range), &item.attrs.doc_strings, - )?; + ) { + Some((def_span, _)) => def_span, + // Cannot give a contiguous span for this link. + None => { + return Err(RedundantExplicitLinksCannotSuggest { + display_link: resolvable_link.clone(), + dest_link: dest.to_string(), + attr_span, + }); + } + }; cx.tcx.emit_node_span_lint( crate::lint::REDUNDANT_EXPLICIT_LINKS, @@ -350,7 +434,7 @@ fn check_reference_redundancy( ); } - None + Ok(()) } fn find_resolution(resolutions: &DocLinkResMap, path: &str) -> Option> { diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs b/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs index 9de8bbe3b8c98..2975ddfcc68bb 100644 --- a/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs +++ b/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs @@ -1,43 +1,42 @@ #![deny(rustdoc::redundant_explicit_links)] +use std::clone::Clone; -// Right now, redundant_explicit_links won't produce a warning at all if -// rustdoc isn't able to calculate an accurate span for the link. -// -// If that changes and this test starts to fail, you should add the -// appropriate annotations, and, also, make sure that it doesn't -// suggest a correction that wipes out the `fn` formal signature -// or the `#[inline]` attribute. - -/// [std::clone::Clone]( +/// [Clone]( pub fn split_outer_inner() { //! std::clone::Clone) +//~^^^ ERROR redundant_explicit_links } -/// [std::clone::Clone](std::clone::Clone +/// [Clone](std::clone::Clone pub fn split_outer_inner_b() { //! ) +//~^^^ ERROR redundant_explicit_links } -/// [std::clone::Clone]( +/// [Clone]( #[inline] /// std::clone::Clone) +//~^^^ ERROR redundant_explicit_links pub fn split_attr() { } -/// [std::clone::Clone](std::clone::Clone +/// [Clone](std::clone::Clone #[inline] /// ) +//~^^^ ERROR redundant_explicit_links pub fn split_attr_b() { } -/// [std::clone::Clone]( +/// [Clone]( /// std::clone::Clone) //~^^ ERROR redundant_explicit_links +//~| SUGGESTION [Clone] pub fn not_split() { } -/// [std::clone::Clone](std::clone::Clone +/// [Clone](std::clone::Clone /// ) //~^^ ERROR redundant_explicit_links +//~| SUGGESTION [Clone] pub fn not_split_b() { } diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr b/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr index 3e2e99f10d9f7..ff9bdfb48fc49 100644 --- a/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr +++ b/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr @@ -1,12 +1,10 @@ error: redundant explicit link target - --> $DIR/redundant_explicit_links_split.rs:33:25 + --> $DIR/redundant_explicit_links_split.rs:4:1 | -LL | /// [std::clone::Clone]( - | ______-----------------__^ - | | | - | | because label contains path that resolves to same destination -LL | | /// std::clone::Clone) - | |_____________________^ explicit target is redundant +LL | / /// [Clone]( +LL | | pub fn split_outer_inner() { +LL | | //! std::clone::Clone) + | |__________________________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination | = note: when a link's destination is not specified, the label is used to resolve intra-doc links @@ -15,18 +13,64 @@ note: the lint level is defined here | LL | #![deny(rustdoc::redundant_explicit_links)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:10:1 + | +LL | / /// [Clone](std::clone::Clone +LL | | pub fn split_outer_inner_b() { +LL | | //! ) + | |_________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:16:1 + | +LL | / /// [Clone]( +LL | | #[inline] +LL | | /// std::clone::Clone) + | |______________________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:23:1 + | +LL | / /// [Clone](std::clone::Clone +LL | | #[inline] +LL | | /// ) + | |_____^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:30:13 + | +LL | /// [Clone]( + | ______-----__^ + | | | + | | because label contains path that resolves to same destination +LL | | /// std::clone::Clone) + | |_____________________^ explicit target is redundant + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links help: remove explicit link target | -LL - /// [std::clone::Clone]( +LL - /// [Clone]( LL - /// std::clone::Clone) -LL + /// [std::clone::Clone] +LL + /// [Clone] | error: redundant explicit link target - --> $DIR/redundant_explicit_links_split.rs:39:25 + --> $DIR/redundant_explicit_links_split.rs:37:13 | -LL | /// [std::clone::Clone](std::clone::Clone - | ______-----------------__^ +LL | /// [Clone](std::clone::Clone + | ______-----__^ | | | | | because label contains path that resolves to same destination LL | | /// ) @@ -36,10 +80,10 @@ LL | | /// ) the label is used to resolve intra-doc links help: remove explicit link target | -LL - /// [std::clone::Clone](std::clone::Clone +LL - /// [Clone](std::clone::Clone LL - /// ) -LL + /// [std::clone::Clone] +LL + /// [Clone] | -error: aborting due to 2 previous errors +error: aborting due to 6 previous errors From 74c203dfbdccbc169eea104bf09778c2b8912bc7 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 17 Jun 2026 20:35:58 -0700 Subject: [PATCH 12/28] Add clarifying comment for shrink_to_hi --- compiler/rustc_resolve/src/rustdoc.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 42a5005552825..7ee5f04ef7243 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -652,6 +652,8 @@ pub fn source_span_for_markdown_range_inner( span_of_all_fragments.split_at(prev_lines_bytes).1.split_at(source_line_len).0; let has_fragment = fragments .iter() + // `source_line_span` might contain indentation that `fragment.span` doesn't contain, + // so `shrink_to_hi()` removes it .any(|fragment| fragment.span.contains(source_line_span.shrink_to_hi())); // Since we're counting bytes, `prev_line_bytes` includes the "\n". prev_lines_bytes += source_line_len + 1; From 162a90c2455223e6fab5d3b540c07851e8818a92 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 18 Jun 2026 12:12:03 -0700 Subject: [PATCH 13/28] Handle block doc comments better --- compiler/rustc_resolve/src/rustdoc.rs | 46 ++++++- .../lints/redundant_explicit_links_split.rs | 58 +++++++++ .../redundant_explicit_links_split.stderr | 118 +++++++++++++++++- 3 files changed, 214 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 7ee5f04ef7243..f1e94b415787b 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -650,14 +650,50 @@ pub fn source_span_for_markdown_range_inner( let source_line_len = u32::try_from(source_line.len()).unwrap(); let source_line_span = span_of_all_fragments.split_at(prev_lines_bytes).1.split_at(source_line_len).0; - let has_fragment = fragments + let fragment = fragments .iter() - // `source_line_span` might contain indentation that `fragment.span` doesn't contain, - // so `shrink_to_hi()` removes it - .any(|fragment| fragment.span.contains(source_line_span.shrink_to_hi())); + // `source_line_span` might contain indentation that `fragment.span` doesn't contain + .find(|fragment| fragment.span.overlaps(source_line_span)); // Since we're counting bytes, `prev_line_bytes` includes the "\n". prev_lines_bytes += source_line_len + 1; - if has_fragment && let Some(offset) = source_line.find(md_line) { + if let Some(fragment) = fragment + && let Some(offset) = source_line.find(md_line) + { + if fragment.span.lo() > source_line_span.lo() + && source_line + [..usize::try_from(fragment.span.lo().0 - source_line_span.lo().0).unwrap()] + .chars() + .any(|c| !c.is_whitespace()) + { + // Make sure anything between the start of this line and the fragment itself is just indentation. + // Because source_line is built by splitting the span that covers all fragments, this only finds + // characters *between* doc comments, not characters before or after doc comments. + // + // 1| /** doc */ + // 2| #[inline] /** doc2 */ + // ^^^^^^^^^ + // | this + // + // 3| fn foo() {} + return None; + } + if fragment.span.hi() < source_line_span.hi() + && source_line + [usize::try_from(fragment.span.hi().0 - source_line_span.lo().0).unwrap()..] + .chars() + .any(|c| !c.is_whitespace()) + { + // Make sure anything between the start of this line and the fragment itself is just indentation. + // Because source_line is built by splitting the span that covers all fragments, this only finds + // characters *between* doc comments, not characters before or after doc comments. + // 1| /** doc */ #[inline] + // ^^^^^^^^^ + // | this + // + // 2| /** doc2 */ fn foo() {} + // 3| fn foo() {} + return None; + } if line_no == starting_line { start_bytes += offset; diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs b/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs index 2975ddfcc68bb..eebc6a0aa023c 100644 --- a/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs +++ b/tests/rustdoc-ui/lints/redundant_explicit_links_split.rs @@ -27,6 +27,64 @@ pub fn split_attr() { pub fn split_attr_b() { } +/** [Clone]( */ #[inline] /** std::clone::Clone) */ +//~^ ERROR redundant_explicit_links +pub fn split_blockcomment_attr() { +} + +/** [Clone](std::clone::Clone */ #[inline] /** ) */ +//~^ ERROR redundant_explicit_links +pub fn split_blockcomment_attr_b() { +} + +/** [Clone]( */ +#[inline] +/** std::clone::Clone) */ +//~^^^ ERROR redundant_explicit_links +pub fn split_blockcomment_multiline_attr() { +} + +/** [Clone](std::clone::Clone */ +#[inline] +/** ) */ +//~^^^ ERROR redundant_explicit_links +pub fn split_blockcomment_multiline_attr_b() { +} + +/** [Clone]( */ #[inline] +/** std::clone::Clone) */ +//~^^ ERROR redundant_explicit_links +pub fn split_blockcomment_twoline_attr() { +} + +/** [Clone](std::clone::Clone */ #[inline] +/** ) */ +//~^^ ERROR redundant_explicit_links +pub fn split_blockcomment_twoline_attr_b() { +} + +/** [Clone]( */ +#[inline] /** std::clone::Clone) */ +//~^^ ERROR redundant_explicit_links +pub fn split_blockcomment_secondline_attr() { +} + +/** [Clone](std::clone::Clone */ +#[inline] /** ) */ +//~^^ ERROR redundant_explicit_links +pub fn split_blockcomment_secondline_attr_b() { +} + +/** [Clone](std::clone::Clone) */ #[inline] +//~^ ERROR redundant_explicit_links +//~| SUGGESTION [Clone] +pub fn split_blockcomment_singleline_attr() { +} + +/** [Clone](std::clone::Clone) */ #[inline] pub fn split_blockcomment_singleline_attr_b() { } +//~^ ERROR redundant_explicit_links +//~| SUGGESTION [Clone] + /// [Clone]( /// std::clone::Clone) //~^^ ERROR redundant_explicit_links diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr b/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr index ff9bdfb48fc49..bfab5b751eedf 100644 --- a/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr +++ b/tests/rustdoc-ui/lints/redundant_explicit_links_split.stderr @@ -48,7 +48,119 @@ LL | | /// ) the label is used to resolve intra-doc links error: redundant explicit link target - --> $DIR/redundant_explicit_links_split.rs:30:13 + --> $DIR/redundant_explicit_links_split.rs:30:1 + | +LL | /** [Clone]( */ #[inline] /** std::clone::Clone) */ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:35:1 + | +LL | /** [Clone](std::clone::Clone */ #[inline] /** ) */ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:40:1 + | +LL | / /** [Clone]( */ +LL | | #[inline] +LL | | /** std::clone::Clone) */ + | |_________________________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:47:1 + | +LL | / /** [Clone](std::clone::Clone */ +LL | | #[inline] +LL | | /** ) */ + | |________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:54:1 + | +LL | / /** [Clone]( */ #[inline] +LL | | /** std::clone::Clone) */ + | |_________________________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:60:1 + | +LL | / /** [Clone](std::clone::Clone */ #[inline] +LL | | /** ) */ + | |________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:66:1 + | +LL | / /** [Clone]( */ +LL | | #[inline] /** std::clone::Clone) */ + | |___________________________________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:72:1 + | +LL | / /** [Clone](std::clone::Clone */ +LL | | #[inline] /** ) */ + | |__________________^ explicit target `std::clone::Clone` is redundant because label `Clone` resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:78:13 + | +LL | /** [Clone](std::clone::Clone) */ #[inline] + | ----- ^^^^^^^^^^^^^^^^^ explicit target is redundant + | | + | because label contains path that resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links +help: remove explicit link target + | +LL - /** [Clone](std::clone::Clone) */ #[inline] +LL + /** [Clone] */ #[inline] + | + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:84:13 + | +LL | /** [Clone](std::clone::Clone) */ #[inline] pub fn split_blockcomment_singleline_attr_b() { } + | ----- ^^^^^^^^^^^^^^^^^ explicit target is redundant + | | + | because label contains path that resolves to same destination + | + = note: when a link's destination is not specified, + the label is used to resolve intra-doc links +help: remove explicit link target + | +LL - /** [Clone](std::clone::Clone) */ #[inline] pub fn split_blockcomment_singleline_attr_b() { } +LL + /** [Clone] */ #[inline] pub fn split_blockcomment_singleline_attr_b() { } + | + +error: redundant explicit link target + --> $DIR/redundant_explicit_links_split.rs:88:13 | LL | /// [Clone]( | ______-----__^ @@ -67,7 +179,7 @@ LL + /// [Clone] | error: redundant explicit link target - --> $DIR/redundant_explicit_links_split.rs:37:13 + --> $DIR/redundant_explicit_links_split.rs:95:13 | LL | /// [Clone](std::clone::Clone | ______-----__^ @@ -85,5 +197,5 @@ LL - /// ) LL + /// [Clone] | -error: aborting due to 6 previous errors +error: aborting due to 16 previous errors From 310bfad291cc0ff04a86fa1510a46f7ebdbe3644 Mon Sep 17 00:00:00 2001 From: sgasho Date: Thu, 4 Jun 2026 00:40:11 +0900 Subject: [PATCH 14/28] Enable Enzyme for x86_64-apple --- src/ci/github-actions/jobs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index f58b385db87ac..74f1a0abdde8e 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -474,7 +474,7 @@ auto: - name: dist-x86_64-apple env: SCRIPT: >- - ./x.py dist bootstrap + ./x.py dist bootstrap enzyme --include-default-paths --host=x86_64-apple-darwin --target=x86_64-apple-darwin From 34e0561ec6c5ff9d6df47491a4dbee71d9d48e1e Mon Sep 17 00:00:00 2001 From: sgasho Date: Thu, 4 Jun 2026 01:16:35 +0900 Subject: [PATCH 15/28] bump download-ci-llvm-stamp --- src/bootstrap/download-ci-llvm-stamp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrap/download-ci-llvm-stamp b/src/bootstrap/download-ci-llvm-stamp index eb3f664e51b35..b61c7b18bad01 100644 --- a/src/bootstrap/download-ci-llvm-stamp +++ b/src/bootstrap/download-ci-llvm-stamp @@ -1,4 +1,4 @@ Change this file to make users of the `download-ci-llvm` configuration download a new version of LLVM from CI, even if the LLVM submodule hasn’t changed. -Last change is for: https://github.com/rust-lang/rust/pull/157557 +Last change is for: https://github.com/rust-lang/rust/pull/157385 From d8470753621c901d239bee25b0d642940c9b15b4 Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas) Chirananthavat" Date: Wed, 1 Jul 2026 16:35:43 +0700 Subject: [PATCH 16/28] Fix conditions of `&` API invalidating memory blocks. --- library/core/src/alloc/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/library/core/src/alloc/mod.rs b/library/core/src/alloc/mod.rs index ec2f106e79364..806f67728efb8 100644 --- a/library/core/src/alloc/mod.rs +++ b/library/core/src/alloc/mod.rs @@ -123,9 +123,11 @@ impl fmt::Display for AllocError { /// * owning an allocator that memory block is allocated with, and /// * not publicly exposing `&mut` access to that allocator. /// -/// Also note that accessing an allocator with `&` access cannot invalidate -/// its memory blocks. Therefore, collections may safely expose `&` access -/// to its allocator. +/// Also note that safe public API of an allocator with `&` access is not +/// allowed to invalidate its memory blocks. Furthermore, unsafe public API +/// of an allocator with `&` access must document that they invalidate +/// memory blocks (e.g., by calling `deallocate`) if they do. Therefore, +/// collections may safely expose `&` access to its allocator. /// /// Also note that, even in cases where are other "alive" allocators known to be /// equivalent to a given collection's allocator, most collections still should From a00bdacb0a83c006f2a4b804cec2cf1bb16a9662 Mon Sep 17 00:00:00 2001 From: Rohan Singla Date: Fri, 3 Jul 2026 03:46:34 +0530 Subject: [PATCH 17/28] diagnostics: suggest type annotation for closure params on HRTB FnOnce mismatch --- .../nice_region_error/placeholder_error.rs | 49 ++++++++++++++++++- .../hrtb-closure-suggest-type-annotation.rs | 43 ++++++++++++++++ ...rtb-closure-suggest-type-annotation.stderr | 29 +++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 tests/ui/closures/hrtb-closure-suggest-type-annotation.rs create mode 100644 tests/ui/closures/hrtb-closure-suggest-type-annotation.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs index e4a2aaff86aed..6b1af2314ed0a 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs @@ -1,13 +1,14 @@ use std::fmt; use rustc_data_structures::intern::Interned; -use rustc_errors::{Diag, IntoDiagArg}; +use rustc_errors::{Applicability, Diag, IntoDiagArg}; +use rustc_hir as hir; use rustc_hir::def::Namespace; use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; use rustc_middle::bug; use rustc_middle::ty::error::ExpectedFound; use rustc_middle::ty::print::{FmtPrinter, Print, PrintTraitRefExt as _, RegionHighlightMode}; -use rustc_middle::ty::{self, GenericArgsRef, RePlaceholder, Region, TyCtxt}; +use rustc_middle::ty::{self, GenericArgsRef, IsSuggestable, RePlaceholder, Region, TyCtxt}; use tracing::{debug, instrument}; use crate::diagnostics::{ @@ -379,6 +380,50 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { } } + // When the mismatched trait is an Fn-trait and the self type is a closure with + // unannotated parameters, suggest adding explicit type annotations. This turns + // the confusing lifetime-generality error into an actionable hint, e.g.: + // |buf| → |buf: &mut [u8]| + if self.tcx().is_fn_trait(trait_def_id) { + let actual_self_ty = self.cx.resolve_vars_if_possible( + ty::TraitRef::new_from_args(self.cx.tcx, trait_def_id, actual_args).self_ty(), + ); + if let ty::Closure(closure_def_id, _) = *actual_self_ty.kind() + && let Some(local_def_id) = closure_def_id.as_local() + && let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Closure(closure), .. }) = + self.tcx().hir_node_by_def_id(local_def_id) + { + let body = self.tcx().hir_body(closure.body); + // For Fn traits, args[1] is the tupled input types (e.g. `(&mut [u8],)`). + let expected_input_tys = expected_args.type_at(1); + if let ty::Tuple(input_tys) = *expected_input_tys.kind() { + let suggestions: Vec<_> = body + .params + .iter() + .zip(input_tys.iter()) + .filter_map(|(param, ty)| { + // ty_span == pat.span means no explicit type annotation was written. + if param.ty_span == param.pat.span + && ty.is_suggestable(self.tcx(), false) + { + Some((param.pat.span.shrink_to_hi(), format!(": {ty}"))) + } else { + None + } + }) + .collect(); + if !suggestions.is_empty() { + err.multipart_suggestion( + "consider adding an explicit type annotation to the closure \ + parameter to resolve the lifetime ambiguity", + suggestions, + Applicability::MaybeIncorrect, + ); + } + } + } + } + err } diff --git a/tests/ui/closures/hrtb-closure-suggest-type-annotation.rs b/tests/ui/closures/hrtb-closure-suggest-type-annotation.rs new file mode 100644 index 0000000000000..0e3904d77517d --- /dev/null +++ b/tests/ui/closures/hrtb-closure-suggest-type-annotation.rs @@ -0,0 +1,43 @@ +// When a closure is passed where a higher-ranked `FnOnce` is expected, but the closure +// parameter has no explicit type annotation, the compiler should suggest adding one. +// See . + +fn inner(buf: &mut [u8], func: F) -> usize +where + F: FnOnce(&mut [u8]) -> Result, +{ + match func(buf) { + Ok(n) => n, + Err(e) => e as usize, + } +} + +fn outer(buf: &mut [u8], func: F) -> usize +where + F: FnOnce(&mut [u8]) -> Result, +{ + let outer_closure = |buf| { + match func(buf) { + Ok(n) => { + println!("OK: {n}"); + Ok(n) + } + Err(e) => { + println!("ERR: {e}"); + Err(e) + } + } + }; + + inner(buf, outer_closure) + //~^ ERROR implementation of `FnOnce` is not general enough + //~| ERROR implementation of `FnOnce` is not general enough +} + +fn main() { + let mut x = [0u8; 16]; + let res = outer(&mut x, |buf| { + if buf.len() % 2 == 0 { Ok(buf.len()) } else { Err(buf.len() as isize) } + }); + println!("{res:?}"); +} diff --git a/tests/ui/closures/hrtb-closure-suggest-type-annotation.stderr b/tests/ui/closures/hrtb-closure-suggest-type-annotation.stderr new file mode 100644 index 0000000000000..3e119f8379a4c --- /dev/null +++ b/tests/ui/closures/hrtb-closure-suggest-type-annotation.stderr @@ -0,0 +1,29 @@ +error: implementation of `FnOnce` is not general enough + --> $DIR/hrtb-closure-suggest-type-annotation.rs:32:5 + | +LL | inner(buf, outer_closure) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough + | + = note: closure with signature `fn(&'2 mut [u8]) -> Result` must implement `FnOnce<(&'1 mut [u8],)>`, for any lifetime `'1`... + = note: ...but it actually implements `FnOnce<(&'2 mut [u8],)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | let outer_closure = |buf: &mut [u8]| { + | +++++++++++ + +error: implementation of `FnOnce` is not general enough + --> $DIR/hrtb-closure-suggest-type-annotation.rs:32:5 + | +LL | inner(buf, outer_closure) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough + | + = note: closure with signature `fn(&'2 mut [u8]) -> Result` must implement `FnOnce<(&'1 mut [u8],)>`, for any lifetime `'1`... + = note: ...but it actually implements `FnOnce<(&'2 mut [u8],)>`, for some specific lifetime `'2` + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | let outer_closure = |buf: &mut [u8]| { + | +++++++++++ + +error: aborting due to 2 previous errors + From 155ead144d38968362b9effee52c12fac3bbbe43 Mon Sep 17 00:00:00 2001 From: Rohan Singla Date: Fri, 3 Jul 2026 03:46:37 +0530 Subject: [PATCH 18/28] test: bless existing UI tests affected by new closure type annotation suggestion --- ...ranked-auto-trait-15.no_assumptions.stderr | 8 +++++++ .../normalize-under-binder/issue-71955.stderr | 16 +++++++++++++ tests/ui/lifetimes/issue-105675.stderr | 24 +++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/tests/ui/async-await/higher-ranked-auto-trait-15.no_assumptions.stderr b/tests/ui/async-await/higher-ranked-auto-trait-15.no_assumptions.stderr index b4f3570d9f2da..ce58bee426b6b 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-15.no_assumptions.stderr +++ b/tests/ui/async-await/higher-ranked-auto-trait-15.no_assumptions.stderr @@ -6,6 +6,10 @@ LL | require_send(future); | = note: closure with signature `fn(&'0 Vec) -> std::slice::Iter<'_, i32>` must implement `FnOnce<(&'1 Vec,)>`, for any two lifetimes `'0` and `'1`... = note: ...but it actually implements `FnOnce<(&Vec,)>` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | for _ in things.iter().map(|n: &Vec| n.iter()).flatten() { + | +++++++++++ error: implementation of `FnOnce` is not general enough --> $DIR/higher-ranked-auto-trait-15.rs:20:5 @@ -16,6 +20,10 @@ LL | require_send(future); = note: closure with signature `fn(&'0 Vec) -> std::slice::Iter<'_, i32>` must implement `FnOnce<(&'1 Vec,)>`, for any two lifetimes `'0` and `'1`... = note: ...but it actually implements `FnOnce<(&Vec,)>` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | for _ in things.iter().map(|n: &Vec| n.iter()).flatten() { + | +++++++++++ error: aborting due to 2 previous errors diff --git a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-71955.stderr b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-71955.stderr index b2bb417a8f01b..a85801fb1078f 100644 --- a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-71955.stderr +++ b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-71955.stderr @@ -6,6 +6,10 @@ LL | foo(bar, "string", |s| s.len() == 5); | = note: closure with signature `for<'a> fn(&'a &'2 str) -> bool` must implement `FnOnce<(&&'1 str,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&&'2 str,)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | foo(bar, "string", |s: &&str| s.len() == 5); + | +++++++ error: implementation of `FnOnce` is not general enough --> $DIR/issue-71955.rs:45:5 @@ -16,6 +20,10 @@ LL | foo(bar, "string", |s| s.len() == 5); = note: closure with signature `for<'a> fn(&'a &'2 str) -> bool` must implement `FnOnce<(&&'1 str,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&&'2 str,)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | foo(bar, "string", |s: &&str| s.len() == 5); + | +++++++ error: implementation of `FnOnce` is not general enough --> $DIR/issue-71955.rs:48:5 @@ -25,6 +33,10 @@ LL | foo(baz, "string", |s| s.0.len() == 5); | = note: closure with signature `for<'a> fn(&'a Wrapper<'2>) -> bool` must implement `FnOnce<(&Wrapper<'1>,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&Wrapper<'2>,)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | foo(baz, "string", |s: &Wrapper<'_>| s.0.len() == 5); + | ++++++++++++++ error: implementation of `FnOnce` is not general enough --> $DIR/issue-71955.rs:48:5 @@ -35,6 +47,10 @@ LL | foo(baz, "string", |s| s.0.len() == 5); = note: closure with signature `for<'a> fn(&'a Wrapper<'2>) -> bool` must implement `FnOnce<(&Wrapper<'1>,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&Wrapper<'2>,)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | foo(baz, "string", |s: &Wrapper<'_>| s.0.len() == 5); + | ++++++++++++++ error: aborting due to 4 previous errors diff --git a/tests/ui/lifetimes/issue-105675.stderr b/tests/ui/lifetimes/issue-105675.stderr index 4b3d0e8ac5efc..a55eb267b104f 100644 --- a/tests/ui/lifetimes/issue-105675.stderr +++ b/tests/ui/lifetimes/issue-105675.stderr @@ -6,6 +6,10 @@ LL | thing(f); | = note: closure with signature `for<'a> fn(&'2 u32, &'a u32, u32)` must implement `FnOnce<(&'1 u32, &u32, u32)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 u32, &u32, u32)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | let f = | _: &u32 , y: &u32 , z: u32 | (); + | ++++++ +++++ error: implementation of `FnOnce` is not general enough --> $DIR/issue-105675.rs:5:5 @@ -16,6 +20,10 @@ LL | thing(f); = note: closure with signature `for<'a> fn(&'2 u32, &'a u32, u32)` must implement `FnOnce<(&'1 u32, &u32, u32)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 u32, &u32, u32)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | let f = | _: &u32 , y: &u32 , z: u32 | (); + | ++++++ +++++ error: implementation of `FnOnce` is not general enough --> $DIR/issue-105675.rs:9:5 @@ -25,6 +33,10 @@ LL | thing(f); | = note: closure with signature `fn(&'2 u32, &u32, u32)` must implement `FnOnce<(&'1 u32, &u32, u32)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 u32, &u32, u32)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | let f = | x: &u32, y: _ , z: u32 | (); + | ++++++ error: implementation of `FnOnce` is not general enough --> $DIR/issue-105675.rs:9:5 @@ -34,6 +46,10 @@ LL | thing(f); | = note: closure with signature `fn(&u32, &'2 u32, u32)` must implement `FnOnce<(&u32, &'1 u32, u32)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&u32, &'2 u32, u32)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | let f = | x: &u32, y: _ , z: u32 | (); + | ++++++ error: implementation of `FnOnce` is not general enough --> $DIR/issue-105675.rs:9:5 @@ -44,6 +60,10 @@ LL | thing(f); = note: closure with signature `fn(&'2 u32, &u32, u32)` must implement `FnOnce<(&'1 u32, &u32, u32)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 u32, &u32, u32)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | let f = | x: &u32, y: _ , z: u32 | (); + | ++++++ error: implementation of `FnOnce` is not general enough --> $DIR/issue-105675.rs:9:5 @@ -54,6 +74,10 @@ LL | thing(f); = note: closure with signature `fn(&u32, &'2 u32, u32)` must implement `FnOnce<(&u32, &'1 u32, u32)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&u32, &'2 u32, u32)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | let f = | x: &u32, y: _ , z: u32 | (); + | ++++++ error: aborting due to 6 previous errors From 5817f14710392ebac8bee0ffa0a10c43c9535816 Mon Sep 17 00:00:00 2001 From: Rohan Singla Date: Fri, 3 Jul 2026 03:51:57 +0530 Subject: [PATCH 19/28] test: bless remaining UI tests affected by new closure type annotation suggestion --- tests/ui/lifetimes/issue-79187-2.stderr | 8 ++++++++ tests/ui/lifetimes/issue-79187.stderr | 8 ++++++++ .../closure-mismatch.current.stderr | 16 ++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/tests/ui/lifetimes/issue-79187-2.stderr b/tests/ui/lifetimes/issue-79187-2.stderr index 78f6ce882dfab..a357332d5a2f0 100644 --- a/tests/ui/lifetimes/issue-79187-2.stderr +++ b/tests/ui/lifetimes/issue-79187-2.stderr @@ -24,6 +24,10 @@ LL | take_foo(|a| a); | = note: closure with signature `fn(&'2 i32) -> &i32` must implement `FnOnce<(&'1 i32,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 i32,)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | take_foo(|a: &i32| a); + | ++++++ error: implementation of `Fn` is not general enough --> $DIR/issue-79187-2.rs:8:5 @@ -33,6 +37,10 @@ LL | take_foo(|a| a); | = note: closure with signature `fn(&'2 i32) -> &i32` must implement `Fn<(&'1 i32,)>`, for any lifetime `'1`... = note: ...but it actually implements `Fn<(&'2 i32,)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | take_foo(|a: &i32| a); + | ++++++ error[E0308]: mismatched types --> $DIR/issue-79187-2.rs:11:5 diff --git a/tests/ui/lifetimes/issue-79187.stderr b/tests/ui/lifetimes/issue-79187.stderr index 8adde8d6dfbf3..59e45f13d6da9 100644 --- a/tests/ui/lifetimes/issue-79187.stderr +++ b/tests/ui/lifetimes/issue-79187.stderr @@ -6,6 +6,10 @@ LL | thing(f); | = note: closure with signature `fn(&'2 u32)` must implement `FnOnce<(&'1 u32,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 u32,)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | let f = |_: &u32| (); + | ++++++ error: implementation of `FnOnce` is not general enough --> $DIR/issue-79187.rs:5:5 @@ -16,6 +20,10 @@ LL | thing(f); = note: closure with signature `fn(&'2 u32)` must implement `FnOnce<(&'1 u32,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 u32,)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | let f = |_: &u32| (); + | ++++++ error: aborting due to 2 previous errors diff --git a/tests/ui/mismatched_types/closure-mismatch.current.stderr b/tests/ui/mismatched_types/closure-mismatch.current.stderr index 378fe83ea89bd..eaa4397c79d1c 100644 --- a/tests/ui/mismatched_types/closure-mismatch.current.stderr +++ b/tests/ui/mismatched_types/closure-mismatch.current.stderr @@ -6,6 +6,10 @@ LL | baz(|_| ()); | = note: closure with signature `fn(&'2 ())` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | baz(|_: &()| ()); + | +++++ error: implementation of `Fn` is not general enough --> $DIR/closure-mismatch.rs:12:5 @@ -15,6 +19,10 @@ LL | baz(|_| ()); | = note: closure with signature `fn(&'2 ())` must implement `Fn<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `Fn<(&'2 (),)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | baz(|_: &()| ()); + | +++++ error: implementation of `FnOnce` is not general enough --> $DIR/closure-mismatch.rs:16:5 @@ -24,6 +32,10 @@ LL | baz(|x| ()); | = note: closure with signature `fn(&'2 ())` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | baz(|x: &()| ()); + | +++++ error: implementation of `Fn` is not general enough --> $DIR/closure-mismatch.rs:16:5 @@ -33,6 +45,10 @@ LL | baz(|x| ()); | = note: closure with signature `fn(&'2 ())` must implement `Fn<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `Fn<(&'2 (),)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity + | +LL | baz(|x: &()| ()); + | +++++ error: aborting due to 4 previous errors From b51d75e3a933cdce36627a0b531acb9521bef510 Mon Sep 17 00:00:00 2001 From: Sylvester Kpei Date: Fri, 3 Jul 2026 20:01:47 -0700 Subject: [PATCH 20/28] Clarify that LocalKey::try_with may return AccessError The docs stated that try_with will return an AccessError when the key has been destroyed. This is not guaranteed: on some platforms, TLS slots can be re-initialized during destruction, so the destroyed state cannot always be observed. Change 'will' to 'may' to accurately reflect the best-effort nature of the check. --- library/std/src/thread/local.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/thread/local.rs b/library/std/src/thread/local.rs index ec0ba9970e479..8684bfab9da60 100644 --- a/library/std/src/thread/local.rs +++ b/library/std/src/thread/local.rs @@ -434,7 +434,7 @@ impl LocalKey { /// /// This will lazily initialize the value if this thread has not referenced /// this key yet. If the key has been destroyed (which may happen if this is called - /// in a destructor), this function will return an [`AccessError`]. + /// in a destructor), this function may return an [`AccessError`]. /// /// # Panics /// From 0e0b98abb450465f196385b5c677b9e70d3e2ed6 Mon Sep 17 00:00:00 2001 From: Rohan Singla Date: Mon, 6 Jul 2026 02:30:55 +0530 Subject: [PATCH 21/28] diagnostics: improve suggestion message and add rustfix test for closure HRTB annotation --- .../nice_region_error/placeholder_error.rs | 12 ++--- ...ranked-auto-trait-15.no_assumptions.stderr | 4 +- ...hrtb-closure-suggest-type-annotation.fixed | 44 +++++++++++++++++++ .../hrtb-closure-suggest-type-annotation.rs | 1 + ...rtb-closure-suggest-type-annotation.stderr | 8 ++-- .../normalize-under-binder/issue-71955.stderr | 8 ++-- tests/ui/lifetimes/issue-105675.stderr | 12 ++--- tests/ui/lifetimes/issue-79187-2.stderr | 4 +- tests/ui/lifetimes/issue-79187.stderr | 4 +- .../closure-mismatch.current.stderr | 8 ++-- 10 files changed, 75 insertions(+), 30 deletions(-) create mode 100644 tests/ui/closures/hrtb-closure-suggest-type-annotation.fixed diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs index 6b1af2314ed0a..f6887c18075a4 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs @@ -413,12 +413,12 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { }) .collect(); if !suggestions.is_empty() { - err.multipart_suggestion( - "consider adding an explicit type annotation to the closure \ - parameter to resolve the lifetime ambiguity", - suggestions, - Applicability::MaybeIncorrect, - ); + let msg = if suggestions.len() == 1 { + "consider adding an explicit type annotation to the closure's argument" + } else { + "consider adding explicit type annotations to the closure's arguments" + }; + err.multipart_suggestion(msg, suggestions, Applicability::MaybeIncorrect); } } } diff --git a/tests/ui/async-await/higher-ranked-auto-trait-15.no_assumptions.stderr b/tests/ui/async-await/higher-ranked-auto-trait-15.no_assumptions.stderr index ce58bee426b6b..461b065d55ad3 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-15.no_assumptions.stderr +++ b/tests/ui/async-await/higher-ranked-auto-trait-15.no_assumptions.stderr @@ -6,7 +6,7 @@ LL | require_send(future); | = note: closure with signature `fn(&'0 Vec) -> std::slice::Iter<'_, i32>` must implement `FnOnce<(&'1 Vec,)>`, for any two lifetimes `'0` and `'1`... = note: ...but it actually implements `FnOnce<(&Vec,)>` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | for _ in things.iter().map(|n: &Vec| n.iter()).flatten() { | +++++++++++ @@ -20,7 +20,7 @@ LL | require_send(future); = note: closure with signature `fn(&'0 Vec) -> std::slice::Iter<'_, i32>` must implement `FnOnce<(&'1 Vec,)>`, for any two lifetimes `'0` and `'1`... = note: ...but it actually implements `FnOnce<(&Vec,)>` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | for _ in things.iter().map(|n: &Vec| n.iter()).flatten() { | +++++++++++ diff --git a/tests/ui/closures/hrtb-closure-suggest-type-annotation.fixed b/tests/ui/closures/hrtb-closure-suggest-type-annotation.fixed new file mode 100644 index 0000000000000..e4a948eb9f7bd --- /dev/null +++ b/tests/ui/closures/hrtb-closure-suggest-type-annotation.fixed @@ -0,0 +1,44 @@ +//@ run-rustfix +// When a closure is passed where a higher-ranked `FnOnce` is expected, but the closure +// parameter has no explicit type annotation, the compiler should suggest adding one. +// See . + +fn inner(buf: &mut [u8], func: F) -> usize +where + F: FnOnce(&mut [u8]) -> Result, +{ + match func(buf) { + Ok(n) => n, + Err(e) => e as usize, + } +} + +fn outer(buf: &mut [u8], func: F) -> usize +where + F: FnOnce(&mut [u8]) -> Result, +{ + let outer_closure = |buf: &mut [u8]| { + match func(buf) { + Ok(n) => { + println!("OK: {n}"); + Ok(n) + } + Err(e) => { + println!("ERR: {e}"); + Err(e) + } + } + }; + + inner(buf, outer_closure) + //~^ ERROR implementation of `FnOnce` is not general enough + //~| ERROR implementation of `FnOnce` is not general enough +} + +fn main() { + let mut x = [0u8; 16]; + let res = outer(&mut x, |buf| { + if buf.len() % 2 == 0 { Ok(buf.len()) } else { Err(buf.len() as isize) } + }); + println!("{res:?}"); +} diff --git a/tests/ui/closures/hrtb-closure-suggest-type-annotation.rs b/tests/ui/closures/hrtb-closure-suggest-type-annotation.rs index 0e3904d77517d..080874d8e236a 100644 --- a/tests/ui/closures/hrtb-closure-suggest-type-annotation.rs +++ b/tests/ui/closures/hrtb-closure-suggest-type-annotation.rs @@ -1,3 +1,4 @@ +//@ run-rustfix // When a closure is passed where a higher-ranked `FnOnce` is expected, but the closure // parameter has no explicit type annotation, the compiler should suggest adding one. // See . diff --git a/tests/ui/closures/hrtb-closure-suggest-type-annotation.stderr b/tests/ui/closures/hrtb-closure-suggest-type-annotation.stderr index 3e119f8379a4c..51691c35f795b 100644 --- a/tests/ui/closures/hrtb-closure-suggest-type-annotation.stderr +++ b/tests/ui/closures/hrtb-closure-suggest-type-annotation.stderr @@ -1,18 +1,18 @@ error: implementation of `FnOnce` is not general enough - --> $DIR/hrtb-closure-suggest-type-annotation.rs:32:5 + --> $DIR/hrtb-closure-suggest-type-annotation.rs:33:5 | LL | inner(buf, outer_closure) | ^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough | = note: closure with signature `fn(&'2 mut [u8]) -> Result` must implement `FnOnce<(&'1 mut [u8],)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 mut [u8],)>`, for some specific lifetime `'2` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | let outer_closure = |buf: &mut [u8]| { | +++++++++++ error: implementation of `FnOnce` is not general enough - --> $DIR/hrtb-closure-suggest-type-annotation.rs:32:5 + --> $DIR/hrtb-closure-suggest-type-annotation.rs:33:5 | LL | inner(buf, outer_closure) | ^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough @@ -20,7 +20,7 @@ LL | inner(buf, outer_closure) = note: closure with signature `fn(&'2 mut [u8]) -> Result` must implement `FnOnce<(&'1 mut [u8],)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 mut [u8],)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | let outer_closure = |buf: &mut [u8]| { | +++++++++++ diff --git a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-71955.stderr b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-71955.stderr index a85801fb1078f..3c6aab12bf137 100644 --- a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-71955.stderr +++ b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-71955.stderr @@ -6,7 +6,7 @@ LL | foo(bar, "string", |s| s.len() == 5); | = note: closure with signature `for<'a> fn(&'a &'2 str) -> bool` must implement `FnOnce<(&&'1 str,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&&'2 str,)>`, for some specific lifetime `'2` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | foo(bar, "string", |s: &&str| s.len() == 5); | +++++++ @@ -20,7 +20,7 @@ LL | foo(bar, "string", |s| s.len() == 5); = note: closure with signature `for<'a> fn(&'a &'2 str) -> bool` must implement `FnOnce<(&&'1 str,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&&'2 str,)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | foo(bar, "string", |s: &&str| s.len() == 5); | +++++++ @@ -33,7 +33,7 @@ LL | foo(baz, "string", |s| s.0.len() == 5); | = note: closure with signature `for<'a> fn(&'a Wrapper<'2>) -> bool` must implement `FnOnce<(&Wrapper<'1>,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&Wrapper<'2>,)>`, for some specific lifetime `'2` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | foo(baz, "string", |s: &Wrapper<'_>| s.0.len() == 5); | ++++++++++++++ @@ -47,7 +47,7 @@ LL | foo(baz, "string", |s| s.0.len() == 5); = note: closure with signature `for<'a> fn(&'a Wrapper<'2>) -> bool` must implement `FnOnce<(&Wrapper<'1>,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&Wrapper<'2>,)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | foo(baz, "string", |s: &Wrapper<'_>| s.0.len() == 5); | ++++++++++++++ diff --git a/tests/ui/lifetimes/issue-105675.stderr b/tests/ui/lifetimes/issue-105675.stderr index a55eb267b104f..db623761b268e 100644 --- a/tests/ui/lifetimes/issue-105675.stderr +++ b/tests/ui/lifetimes/issue-105675.stderr @@ -6,7 +6,7 @@ LL | thing(f); | = note: closure with signature `for<'a> fn(&'2 u32, &'a u32, u32)` must implement `FnOnce<(&'1 u32, &u32, u32)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 u32, &u32, u32)>`, for some specific lifetime `'2` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding explicit type annotations to the closure's arguments | LL | let f = | _: &u32 , y: &u32 , z: u32 | (); | ++++++ +++++ @@ -20,7 +20,7 @@ LL | thing(f); = note: closure with signature `for<'a> fn(&'2 u32, &'a u32, u32)` must implement `FnOnce<(&'1 u32, &u32, u32)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 u32, &u32, u32)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding explicit type annotations to the closure's arguments | LL | let f = | _: &u32 , y: &u32 , z: u32 | (); | ++++++ +++++ @@ -33,7 +33,7 @@ LL | thing(f); | = note: closure with signature `fn(&'2 u32, &u32, u32)` must implement `FnOnce<(&'1 u32, &u32, u32)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 u32, &u32, u32)>`, for some specific lifetime `'2` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | let f = | x: &u32, y: _ , z: u32 | (); | ++++++ @@ -46,7 +46,7 @@ LL | thing(f); | = note: closure with signature `fn(&u32, &'2 u32, u32)` must implement `FnOnce<(&u32, &'1 u32, u32)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&u32, &'2 u32, u32)>`, for some specific lifetime `'2` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | let f = | x: &u32, y: _ , z: u32 | (); | ++++++ @@ -60,7 +60,7 @@ LL | thing(f); = note: closure with signature `fn(&'2 u32, &u32, u32)` must implement `FnOnce<(&'1 u32, &u32, u32)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 u32, &u32, u32)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | let f = | x: &u32, y: _ , z: u32 | (); | ++++++ @@ -74,7 +74,7 @@ LL | thing(f); = note: closure with signature `fn(&u32, &'2 u32, u32)` must implement `FnOnce<(&u32, &'1 u32, u32)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&u32, &'2 u32, u32)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | let f = | x: &u32, y: _ , z: u32 | (); | ++++++ diff --git a/tests/ui/lifetimes/issue-79187-2.stderr b/tests/ui/lifetimes/issue-79187-2.stderr index a357332d5a2f0..2731d0972d2bc 100644 --- a/tests/ui/lifetimes/issue-79187-2.stderr +++ b/tests/ui/lifetimes/issue-79187-2.stderr @@ -24,7 +24,7 @@ LL | take_foo(|a| a); | = note: closure with signature `fn(&'2 i32) -> &i32` must implement `FnOnce<(&'1 i32,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 i32,)>`, for some specific lifetime `'2` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | take_foo(|a: &i32| a); | ++++++ @@ -37,7 +37,7 @@ LL | take_foo(|a| a); | = note: closure with signature `fn(&'2 i32) -> &i32` must implement `Fn<(&'1 i32,)>`, for any lifetime `'1`... = note: ...but it actually implements `Fn<(&'2 i32,)>`, for some specific lifetime `'2` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | take_foo(|a: &i32| a); | ++++++ diff --git a/tests/ui/lifetimes/issue-79187.stderr b/tests/ui/lifetimes/issue-79187.stderr index 59e45f13d6da9..e08400481e470 100644 --- a/tests/ui/lifetimes/issue-79187.stderr +++ b/tests/ui/lifetimes/issue-79187.stderr @@ -6,7 +6,7 @@ LL | thing(f); | = note: closure with signature `fn(&'2 u32)` must implement `FnOnce<(&'1 u32,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 u32,)>`, for some specific lifetime `'2` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | let f = |_: &u32| (); | ++++++ @@ -20,7 +20,7 @@ LL | thing(f); = note: closure with signature `fn(&'2 u32)` must implement `FnOnce<(&'1 u32,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 u32,)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | let f = |_: &u32| (); | ++++++ diff --git a/tests/ui/mismatched_types/closure-mismatch.current.stderr b/tests/ui/mismatched_types/closure-mismatch.current.stderr index eaa4397c79d1c..82e3e4b0f78f2 100644 --- a/tests/ui/mismatched_types/closure-mismatch.current.stderr +++ b/tests/ui/mismatched_types/closure-mismatch.current.stderr @@ -6,7 +6,7 @@ LL | baz(|_| ()); | = note: closure with signature `fn(&'2 ())` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | baz(|_: &()| ()); | +++++ @@ -19,7 +19,7 @@ LL | baz(|_| ()); | = note: closure with signature `fn(&'2 ())` must implement `Fn<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `Fn<(&'2 (),)>`, for some specific lifetime `'2` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | baz(|_: &()| ()); | +++++ @@ -32,7 +32,7 @@ LL | baz(|x| ()); | = note: closure with signature `fn(&'2 ())` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | baz(|x: &()| ()); | +++++ @@ -45,7 +45,7 @@ LL | baz(|x| ()); | = note: closure with signature `fn(&'2 ())` must implement `Fn<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `Fn<(&'2 (),)>`, for some specific lifetime `'2` -help: consider adding an explicit type annotation to the closure parameter to resolve the lifetime ambiguity +help: consider adding an explicit type annotation to the closure's argument | LL | baz(|x: &()| ()); | +++++ From 049b3a32ab90edb3bc1d6d299145d49f9f0adc26 Mon Sep 17 00:00:00 2001 From: Rohan Singla Date: Mon, 6 Jul 2026 03:40:43 +0530 Subject: [PATCH 22/28] test: bless nll UI tests affected by new closure type annotation suggestion --- tests/ui/nll/ice-106874.stderr | 32 +++++++++++++++++++ tests/ui/nll/issue-57843.stderr | 4 +++ ...missing-universe-cause-issue-114907.stderr | 16 ++++++++++ ...insensitive-scopes-issue-117146.nll.stderr | 8 +++++ ...sitive-scopes-issue-117146.polonius.stderr | 8 +++++ 5 files changed, 68 insertions(+) diff --git a/tests/ui/nll/ice-106874.stderr b/tests/ui/nll/ice-106874.stderr index 629570b602ed6..45dd1557dd027 100644 --- a/tests/ui/nll/ice-106874.stderr +++ b/tests/ui/nll/ice-106874.stderr @@ -6,6 +6,10 @@ LL | A(B(C::new(D::new(move |st| f(st))))) | = note: closure with signature `fn(&'0 mut V)` must implement `FnOnce<(&mut V,)>`, for some specific lifetime `'0`... = note: ...but it actually implements `FnOnce<(&'1 mut V,)>`, for some specific lifetime `'1` +help: consider adding an explicit type annotation to the closure's argument + | +LL | A(B(C::new(D::new(move |st: &mut V| f(st))))) + | ++++++++ error: implementation of `FnOnce` is not general enough --> $DIR/ice-106874.rs:8:5 @@ -16,6 +20,10 @@ LL | A(B(C::new(D::new(move |st| f(st))))) = note: closure with signature `fn(&'0 mut V)` must implement `FnOnce<(&mut V,)>`, for some specific lifetime `'0`... = note: ...but it actually implements `FnOnce<(&'1 mut V,)>`, for some specific lifetime `'1` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure's argument + | +LL | A(B(C::new(D::new(move |st: &mut V| f(st))))) + | ++++++++ error: higher-ranked subtype error --> $DIR/ice-106874.rs:8:5 @@ -39,6 +47,10 @@ LL | A(B(C::new(D::new(move |st| f(st))))) | = note: closure with signature `fn(&'2 mut V)` must implement `FnOnce<(&'1 mut V,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 mut V,)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure's argument + | +LL | A(B(C::new(D::new(move |st: &mut V| f(st))))) + | ++++++++ error: implementation of `Fn` is not general enough --> $DIR/ice-106874.rs:8:7 @@ -48,6 +60,10 @@ LL | A(B(C::new(D::new(move |st| f(st))))) | = note: closure with signature `fn(&'2 mut V)` must implement `Fn<(&'1 mut V,)>`, for any lifetime `'1`... = note: ...but it actually implements `Fn<(&'2 mut V,)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure's argument + | +LL | A(B(C::new(D::new(move |st: &mut V| f(st))))) + | ++++++++ error: implementation of `FnOnce` is not general enough --> $DIR/ice-106874.rs:8:7 @@ -58,6 +74,10 @@ LL | A(B(C::new(D::new(move |st| f(st))))) = note: closure with signature `fn(&'2 mut V)` must implement `FnOnce<(&'1 mut V,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 mut V,)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure's argument + | +LL | A(B(C::new(D::new(move |st: &mut V| f(st))))) + | ++++++++ error: implementation of `Fn` is not general enough --> $DIR/ice-106874.rs:8:7 @@ -68,6 +88,10 @@ LL | A(B(C::new(D::new(move |st| f(st))))) = note: closure with signature `fn(&'2 mut V)` must implement `Fn<(&'1 mut V,)>`, for any lifetime `'1`... = note: ...but it actually implements `Fn<(&'2 mut V,)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure's argument + | +LL | A(B(C::new(D::new(move |st: &mut V| f(st))))) + | ++++++++ error: implementation of `FnOnce` is not general enough --> $DIR/ice-106874.rs:8:7 @@ -78,6 +102,10 @@ LL | A(B(C::new(D::new(move |st| f(st))))) = note: closure with signature `fn(&'2 mut V)` must implement `FnOnce<(&'1 mut V,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 mut V,)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure's argument + | +LL | A(B(C::new(D::new(move |st: &mut V| f(st))))) + | ++++++++ error: implementation of `Fn` is not general enough --> $DIR/ice-106874.rs:8:7 @@ -88,6 +116,10 @@ LL | A(B(C::new(D::new(move |st| f(st))))) = note: closure with signature `fn(&'2 mut V)` must implement `Fn<(&'1 mut V,)>`, for any lifetime `'1`... = note: ...but it actually implements `Fn<(&'2 mut V,)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure's argument + | +LL | A(B(C::new(D::new(move |st: &mut V| f(st))))) + | ++++++++ error: aborting due to 10 previous errors diff --git a/tests/ui/nll/issue-57843.stderr b/tests/ui/nll/issue-57843.stderr index eed511460cac7..386f952c0541d 100644 --- a/tests/ui/nll/issue-57843.stderr +++ b/tests/ui/nll/issue-57843.stderr @@ -6,6 +6,10 @@ LL | Foo(Box::new(|_| ())); | = note: closure with signature `fn(&'2 bool)` must implement `FnOnce<(&'1 bool,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 bool,)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure's argument + | +LL | Foo(Box::new(|_: &'a bool| ())); + | ++++++++++ error: aborting due to 1 previous error diff --git a/tests/ui/nll/missing-universe-cause-issue-114907.stderr b/tests/ui/nll/missing-universe-cause-issue-114907.stderr index c2e91edd13872..d8922ef80e813 100644 --- a/tests/ui/nll/missing-universe-cause-issue-114907.stderr +++ b/tests/ui/nll/missing-universe-cause-issue-114907.stderr @@ -6,6 +6,10 @@ LL | accept(callback); | = note: closure with signature `fn(&'2 ())` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure's argument + | +LL | let callback = |_: &()| {}; + | +++++ error: implementation of `FnOnce` is not general enough --> $DIR/missing-universe-cause-issue-114907.rs:33:5 @@ -16,6 +20,10 @@ LL | accept(callback); = note: closure with signature `fn(&'2 ())` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure's argument + | +LL | let callback = |_: &()| {}; + | +++++ error: implementation of `FnOnce` is not general enough --> $DIR/missing-universe-cause-issue-114907.rs:33:5 @@ -26,6 +34,10 @@ LL | accept(callback); = note: closure with signature `fn(&'2 ())` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure's argument + | +LL | let callback = |_: &()| {}; + | +++++ error: implementation of `FnOnce` is not general enough --> $DIR/missing-universe-cause-issue-114907.rs:33:5 @@ -36,6 +48,10 @@ LL | accept(callback); = note: closure with signature `fn(&'2 ())` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: consider adding an explicit type annotation to the closure's argument + | +LL | let callback = |_: &()| {}; + | +++++ error: higher-ranked subtype error --> $DIR/missing-universe-cause-issue-114907.rs:33:5 diff --git a/tests/ui/nll/polonius/location-insensitive-scopes-issue-117146.nll.stderr b/tests/ui/nll/polonius/location-insensitive-scopes-issue-117146.nll.stderr index 804b3f00a2642..0734eba1496d7 100644 --- a/tests/ui/nll/polonius/location-insensitive-scopes-issue-117146.nll.stderr +++ b/tests/ui/nll/polonius/location-insensitive-scopes-issue-117146.nll.stderr @@ -32,6 +32,10 @@ LL | bad(&b); | = note: closure with signature `fn(&'2 ()) -> &()` must implement `Fn<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `Fn<(&'2 (),)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure's argument + | +LL | let b = |_: &()| &a; + | +++++ error: implementation of `FnOnce` is not general enough --> $DIR/location-insensitive-scopes-issue-117146.rs:13:5 @@ -41,6 +45,10 @@ LL | bad(&b); | = note: closure with signature `fn(&'2 ()) -> &()` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure's argument + | +LL | let b = |_: &()| &a; + | +++++ error: aborting due to 3 previous errors diff --git a/tests/ui/nll/polonius/location-insensitive-scopes-issue-117146.polonius.stderr b/tests/ui/nll/polonius/location-insensitive-scopes-issue-117146.polonius.stderr index 804b3f00a2642..0734eba1496d7 100644 --- a/tests/ui/nll/polonius/location-insensitive-scopes-issue-117146.polonius.stderr +++ b/tests/ui/nll/polonius/location-insensitive-scopes-issue-117146.polonius.stderr @@ -32,6 +32,10 @@ LL | bad(&b); | = note: closure with signature `fn(&'2 ()) -> &()` must implement `Fn<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `Fn<(&'2 (),)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure's argument + | +LL | let b = |_: &()| &a; + | +++++ error: implementation of `FnOnce` is not general enough --> $DIR/location-insensitive-scopes-issue-117146.rs:13:5 @@ -41,6 +45,10 @@ LL | bad(&b); | = note: closure with signature `fn(&'2 ()) -> &()` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2` +help: consider adding an explicit type annotation to the closure's argument + | +LL | let b = |_: &()| &a; + | +++++ error: aborting due to 3 previous errors From 448e0f865fdcbfbe5e45202f8cf568f7bb25a108 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Mon, 6 Jul 2026 15:05:28 +0900 Subject: [PATCH 23/28] add regression test for final RPITIT override panic --- tests/ui/traits/final/overriding-rpitit.rs | 18 ++++++++++++++++++ tests/ui/traits/final/overriding-rpitit.stderr | 14 ++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/ui/traits/final/overriding-rpitit.rs create mode 100644 tests/ui/traits/final/overriding-rpitit.stderr diff --git a/tests/ui/traits/final/overriding-rpitit.rs b/tests/ui/traits/final/overriding-rpitit.rs new file mode 100644 index 0000000000000..b44b470175ce4 --- /dev/null +++ b/tests/ui/traits/final/overriding-rpitit.rs @@ -0,0 +1,18 @@ +#![feature(final_associated_functions)] + +// Regression test for https://github.com/rust-lang/rust/issues/158824. + +trait Item { + final fn bar() -> impl Clone { + todo!() + } +} + +struct Foo; + +impl Item for Foo { + fn bar() -> impl Clone {} + //~^ ERROR cannot override `bar` because it already has a `final` definition in the trait +} + +fn main() {} diff --git a/tests/ui/traits/final/overriding-rpitit.stderr b/tests/ui/traits/final/overriding-rpitit.stderr new file mode 100644 index 0000000000000..b865171472bab --- /dev/null +++ b/tests/ui/traits/final/overriding-rpitit.stderr @@ -0,0 +1,14 @@ +error: cannot override `bar` because it already has a `final` definition in the trait + --> $DIR/overriding-rpitit.rs:14:5 + | +LL | fn bar() -> impl Clone {} + | ^^^^^^^^^^^^^^^^^^^^^^ + | +note: `bar` is marked final here + --> $DIR/overriding-rpitit.rs:6:5 + | +LL | final fn bar() -> impl Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 5a5f78dd6ab10e1e5ac75cc1ec11431d6ba14567 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Mon, 6 Jul 2026 15:05:43 +0900 Subject: [PATCH 24/28] avoid final override diagnostics for non-function associated items --- compiler/rustc_hir_analysis/src/check/check.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index aa204a20e2d57..c3dff49be9927 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -1254,7 +1254,7 @@ fn check_overriding_final_trait_item<'tcx>( trait_item: ty::AssocItem, impl_item: ty::AssocItem, ) { - if trait_item.defaultness(tcx).is_final() { + if trait_item.is_fn() && trait_item.defaultness(tcx).is_final() { tcx.dcx().emit_err(diagnostics::OverridingFinalTraitFunction { impl_span: tcx.def_span(impl_item.def_id), trait_span: tcx.def_span(trait_item.def_id), From 042e779afae728bfd491bcfb690908dc762d82b5 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 7 Jul 2026 00:15:26 -0700 Subject: [PATCH 25/28] Add diagram to comment Co-authored-by: lolbinarycat --- compiler/rustc_resolve/src/rustdoc.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index f1e94b415787b..301ea7c71da06 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -719,6 +719,12 @@ pub fn source_span_for_markdown_range_inner( // but we found a non-empty non-markdown line. // This could be an attribute, and we don't want a diagnostic // suggesting to delete that attribute, so we return None to be safe. + // 1| /** doc */ + // 2 | #[inline] + // ^^^^^^^^^ + // | this + // 3| /** doc2 */ + // 4| fn foo() {} return None; } else { end_bytes += source_line.len() + 1; From 8f1fd2c6e4bf7e44dfca254e162120d260144983 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 7 Jul 2026 00:16:01 -0700 Subject: [PATCH 26/28] Fix wrong diagram in comment Co-authored-by: lolbinarycat --- compiler/rustc_resolve/src/rustdoc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 301ea7c71da06..ddf5600f76659 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -690,7 +690,7 @@ pub fn source_span_for_markdown_range_inner( // ^^^^^^^^^ // | this // - // 2| /** doc2 */ fn foo() {} + // 2| /** doc2 */ // 3| fn foo() {} return None; } From fd07857db3c77538b9214c4125aaa91ee48481d4 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 7 Jul 2026 00:17:23 -0700 Subject: [PATCH 27/28] Use better name for lint struct Co-authored-by: lolbinarycat --- .../passes/lint/redundant_explicit_links.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs index ad45b6e1d1c5b..cd7b7caac69ad 100644 --- a/src/librustdoc/passes/lint/redundant_explicit_links.rs +++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs @@ -154,13 +154,13 @@ fn check_redundant_explicit_link<'md>( } } -struct RedundantExplicitLinksCannotSuggest { +struct RedundantExplicitLinksWithoutSuggestion { attr_span: Span, display_link: String, dest_link: String, } -impl<'a> Diagnostic<'a, ()> for RedundantExplicitLinksCannotSuggest { +impl<'a> Diagnostic<'a, ()> for RedundantExplicitLinksWithoutSuggestion { fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { let Self { attr_span, display_link, dest_link } = self; @@ -186,7 +186,7 @@ fn check_inline_or_reference_unknown_redundancy( dest: String, link_data: LinkData, (open, close): (u8, u8), -) -> Result<(), RedundantExplicitLinksCannotSuggest> { +) -> Result<(), RedundantExplicitLinksWithoutSuggestion> { struct RedundantExplicitLinks { explicit_span: Span, display_span: Span, @@ -256,7 +256,7 @@ fn check_inline_or_reference_unknown_redundancy( Some((_, true)) => return Ok(()), // Cannot give a contiguous span for this link. None => { - return Err(RedundantExplicitLinksCannotSuggest { + return Err(RedundantExplicitLinksWithoutSuggestion { display_link: resolvable_link.clone(), dest_link: dest.to_string(), attr_span, @@ -274,7 +274,7 @@ fn check_inline_or_reference_unknown_redundancy( Some((_, true)) => return Ok(()), // Cannot give a contiguous span for this link. None => { - return Err(RedundantExplicitLinksCannotSuggest { + return Err(RedundantExplicitLinksWithoutSuggestion { display_link: resolvable_link.clone(), dest_link: dest.to_string(), attr_span, @@ -308,7 +308,7 @@ fn check_reference_redundancy( link_range: Range, dest: &CowStr<'_>, link_data: LinkData, -) -> Result<(), RedundantExplicitLinksCannotSuggest> { +) -> Result<(), RedundantExplicitLinksWithoutSuggestion> { struct RedundantExplicitLinkTarget { explicit_span: Span, display_span: Span, @@ -378,7 +378,7 @@ fn check_reference_redundancy( Some((_, true)) => return Ok(()), // Cannot give a contiguous span for this link. None => { - return Err(RedundantExplicitLinksCannotSuggest { + return Err(RedundantExplicitLinksWithoutSuggestion { display_link: resolvable_link.clone(), dest_link: dest.to_string(), attr_span, @@ -396,7 +396,7 @@ fn check_reference_redundancy( Some((_, true)) => return Ok(()), // Cannot give a contiguous span for this link. None => { - return Err(RedundantExplicitLinksCannotSuggest { + return Err(RedundantExplicitLinksWithoutSuggestion { display_link: resolvable_link.clone(), dest_link: dest.to_string(), attr_span, @@ -412,7 +412,7 @@ fn check_reference_redundancy( Some((def_span, _)) => def_span, // Cannot give a contiguous span for this link. None => { - return Err(RedundantExplicitLinksCannotSuggest { + return Err(RedundantExplicitLinksWithoutSuggestion { display_link: resolvable_link.clone(), dest_link: dest.to_string(), attr_span, From 49769afe4b36702a816eb8bde0230d3b0e194e0c Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Fri, 8 May 2026 16:07:42 +0200 Subject: [PATCH 28/28] QNX SDP 8 fixes. 1) setsid/setpgrp: check child's group process ID Linux [1], FreeBSD [2] and the Open Group [3] manuals for both setsid and setpgrp state that the calling process will be assigned a process group ID that matches its process ID so check that's the case in the test by calling getpgid on the child process ID. on QNX8 (as of GA8.0.2) libc::kill(pgid) appears unable to terminate the child process so skip that check on that target as it's behavior that libstd provides no API for [1]: https://man7.org/linux/man-pages/man2/setsid.2.html [2]: https://man.freebsd.org/cgi/man.cgi?query=setsid&sektion=2 [3]: https://pubs.opengroup.org/onlinepubs/000095399/functions/setsid.html 2) fix handling of sockaddr_un.len on QNX also extend the existing tests to exercise the getsockname(2) and getpeername(2) paths with both named and unnamed Unix sockets 3) Additional fixes library/std after earlier PR renamed some targets 4) Replace more instances of "QNX Neutrino / QNX OS" with "QNX SDP" and fix up some documentation URLs 5) Tests ignored on target_os = "nto" should also be ignored on target_os = "qnx". 6) Simplify nto-qnx.md documentation cc-rs now knows about QNX so the environment isn't required --- library/std/src/os/unix/net/addr.rs | 18 +++++- library/std/src/os/unix/net/stream.rs | 1 + library/std/src/os/unix/net/tests.rs | 10 +++ library/std/src/os/unix/process.rs | 4 +- library/std/src/sys/pal/unix/time.rs | 4 +- library/std/src/sys/paths/unix.rs | 2 +- .../std/src/sys/process/unix/common/tests.rs | 43 +++++++++++-- library/std/src/sys/process/unix/unix.rs | 10 +-- library/std/src/sys/thread/unix.rs | 9 +-- library/std/src/sys/thread_local/key/racy.rs | 8 +-- src/doc/rustc/src/platform-support.md | 12 ++-- src/doc/rustc/src/platform-support/nto-qnx.md | 61 ++++++------------- .../src/directives/directive_names.rs | 1 + tests/codegen-llvm/thread-local.rs | 1 + tests/ui/abi/stack-probes-lto.rs | 3 +- tests/ui/abi/stack-probes.rs | 3 +- tests/ui/command/command-setgroups.rs | 1 + tests/ui/process/process-spawn-failure.rs | 1 + tests/ui/runtime/out-of-stack.rs | 1 + .../runtime/signal-alternate-stack-cleanup.rs | 1 + .../suggestions/missing-lifetime-specifier.rs | 1 + .../missing-lifetime-specifier.stderr | 18 +++--- 22 files changed, 128 insertions(+), 85 deletions(-) diff --git a/library/std/src/os/unix/net/addr.rs b/library/std/src/os/unix/net/addr.rs index 304f2f23828de..e13f44d6fc9bd 100644 --- a/library/std/src/os/unix/net/addr.rs +++ b/library/std/src/os/unix/net/addr.rs @@ -53,7 +53,15 @@ pub(super) fn sockaddr_un(path: &Path) -> io::Result<(libc::sockaddr_un, libc::s let mut len = SUN_PATH_OFFSET + bytes.len(); match bytes.get(0) { Some(&0) | None => {} - Some(_) => len += 1, + Some(_) => { + // on QNX7.1 and QNX8 the `len` value returned by the SUN_LEN + // macro in its libc does not include the null byte in the count so + // don't add it here to match what a C program passes to bind(2) and + // similar functions + if cfg!(not(any(target_os = "qnx", target_env = "nto71"))) { + len += 1 + } + } } Ok((addr, len as libc::socklen_t)) } @@ -247,7 +255,13 @@ impl SocketAddr { } else if self.addr.sun_path[0] == 0 { AddressKind::Abstract(ByteStr::from_bytes(&path[1..len])) } else { - AddressKind::Pathname(OsStr::from_bytes(&path[..len - 1]).as_ref()) + // the value returned by getsockname(2) and similar on QNX7.1 and + // QNX8 does not count the NUL byte terminator of the path string, + // which matches the behavior of the SUN_LEN macro in libc, but + // other OSes do count the NUL byte so adjust accordingly + let end = + if cfg!(any(target_os = "qnx", target_env = "nto71")) { len } else { len - 1 }; + AddressKind::Pathname(OsStr::from_bytes(&path[..end]).as_ref()) } } } diff --git a/library/std/src/os/unix/net/stream.rs b/library/std/src/os/unix/net/stream.rs index 4dfa06d74ce7e..a50b10539ebf5 100644 --- a/library/std/src/os/unix/net/stream.rs +++ b/library/std/src/os/unix/net/stream.rs @@ -256,6 +256,7 @@ impl UnixStream { target_os = "netbsd", target_os = "openbsd", target_os = "nto", + target_os = "qnx", target_vendor = "apple", target_os = "cygwin" ))] diff --git a/library/std/src/os/unix/net/tests.rs b/library/std/src/os/unix/net/tests.rs index f703fc6d54c02..3ba4b44d2f1ef 100644 --- a/library/std/src/os/unix/net/tests.rs +++ b/library/std/src/os/unix/net/tests.rs @@ -9,6 +9,7 @@ use crate::os::cygwin::net::{SocketAddrExt, UnixSocketExt}; use crate::os::linux::net::{SocketAddrExt, UnixSocketExt}; #[cfg(any(target_os = "android", target_os = "linux"))] use crate::os::unix::io::AsRawFd; +use crate::path::Path; use crate::test_helpers::tmpdir; use crate::thread; use crate::time::Duration; @@ -22,6 +23,12 @@ macro_rules! or_panic { }; } +#[test] +fn sock_addr_from_pathname() { + let address = or_panic!(SocketAddr::from_pathname("/path/to/socket")); + assert_eq!(address.as_pathname(), Some(Path::new("/path/to/socket"))); +} + #[test] #[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets #[cfg_attr(target_os = "vxworks", ignore = "Unix sockets are not implemented in VxWorks")] @@ -32,6 +39,7 @@ fn basic() { let msg2 = b"world!"; let listener = or_panic!(UnixListener::bind(&socket_path)); + assert_eq!(Some(&*socket_path), listener.local_addr().unwrap().as_pathname()); let thread = thread::spawn(move || { let mut stream = or_panic!(listener.accept()).0; let mut buf = [0; 5]; @@ -79,6 +87,8 @@ fn pair() { let msg2 = b"world!"; let (mut s1, mut s2) = or_panic!(UnixStream::pair()); + assert!(s1.local_addr().unwrap().is_unnamed()); + assert!(s2.peer_addr().unwrap().is_unnamed()); let thread = thread::spawn(move || { // s1 must be moved in or the test will hang! let mut buf = [0; 5]; diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index dcec7b2a1077c..d1b0114604080 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -19,9 +19,9 @@ cfg_select! { type GroupId = u16; } any(target_os = "nto", target_os = "qnx") => { - // Both IDs are signed, see `sys/target_nto.h` of the QNX Neutrino SDP. + // Both IDs are signed, see `sys/target_nto.h` of the QNX SDP. // Only positive values should be used, see e.g. - // https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/s/setuid.html + // https://www.qnx.com/developers/docs/7.1/com.qnx.doc.neutrino.lib_ref/topic/s/setuid.html type UserId = i32; type GroupId = i32; } diff --git a/library/std/src/sys/pal/unix/time.rs b/library/std/src/sys/pal/unix/time.rs index 26296b5d169a1..9c4ba9f5f388a 100644 --- a/library/std/src/sys/pal/unix/time.rs +++ b/library/std/src/sys/pal/unix/time.rs @@ -177,9 +177,9 @@ impl Timespec { }) } - // On QNX Neutrino, the maximum timespec for e.g. pthread_cond_timedwait + // On QNX, the maximum timespec for e.g. pthread_cond_timedwait // is 2^64 nanoseconds - #[cfg(target_os = "nto")] + #[cfg(any(target_os = "nto", target_os = "qnx"))] pub(in crate::sys) fn to_timespec_capped(&self) -> Option { // Check if timeout in nanoseconds would fit into an u64 if (self.tv_nsec.as_inner() as u64) diff --git a/library/std/src/sys/paths/unix.rs b/library/std/src/sys/paths/unix.rs index 36879b7b9bce3..a2b4f34e54b39 100644 --- a/library/std/src/sys/paths/unix.rs +++ b/library/std/src/sys/paths/unix.rs @@ -272,7 +272,7 @@ pub fn current_exe() -> io::Result { #[cfg(any(target_os = "nto", target_os = "qnx"))] pub fn current_exe() -> io::Result { let mut e = crate::fs::read("/proc/self/exefile")?; - // Current versions of QNX Neutrino provide a null-terminated path. + // Current versions of QNX SDP provide a null-terminated path. // Ensure the trailing null byte is not returned here. if let Some(0) = e.last() { e.pop(); diff --git a/library/std/src/sys/process/unix/common/tests.rs b/library/std/src/sys/process/unix/common/tests.rs index 5f71bf051f8a8..bc1d158b74861 100644 --- a/library/std/src/sys/process/unix/common/tests.rs +++ b/library/std/src/sys/process/unix/common/tests.rs @@ -99,8 +99,13 @@ fn test_process_group_posix_spawn() { cmd.stdout(Stdio::MakePipe); let (mut cat, _pipes) = t!(cmd.spawn(Stdio::Null, true)); + let pid = cat.id() as libc::pid_t; + let pgid = libc::getpgid(pid); + assert_ne!(-1, pgid, "getpgid failed"); + assert_eq!(pid, pgid, "child process ID does not match its process group ID"); + // Check that we can kill its process group, which means there *is* one. - t!(cvt(libc::kill(-(cat.id() as libc::pid_t), libc::SIGINT))); + t!(cvt(libc::kill(-pgid, libc::SIGINT))); t!(cat.wait()); } @@ -127,8 +132,18 @@ fn test_process_group_no_posix_spawn() { cmd.stdout(Stdio::MakePipe); let (mut cat, _pipes) = t!(cmd.spawn(Stdio::Null, true)); + let pid = cat.id() as libc::pid_t; + let pgid = libc::getpgid(pid); + assert_ne!(-1, pgid, "getpgid failed"); + assert_eq!(pid, pgid, "child process ID does not match its process group ID"); + + if cfg!(target_os = "qnx") { + // kill(-pgid) appears to be unable to terminate the child process on QNX8 + return; + } + // Check that we can kill its process group, which means there *is* one. - t!(cvt(libc::kill(-(cat.id() as libc::pid_t), libc::SIGINT))); + t!(cvt(libc::kill(-pgid, libc::SIGINT))); t!(cat.wait()); } @@ -154,9 +169,19 @@ fn test_setsid_posix_spawn() { let (mut cat, _pipes) = t!(cmd.spawn(Stdio::Null, true)); unsafe { + let pid = cat.id() as libc::pid_t; + let pgid = libc::getpgid(pid); + assert_ne!(-1, pgid, "getpgid failed"); + assert_eq!(pid, pgid, "child process ID does not match its process group ID"); + + if cfg!(target_os = "qnx") { + // kill(-pgid) appears to be unable to terminate the child process on QNX8 + return; + } + // Setsid will create a new session and process group, so check that // we can kill the process group, which means there *is* one. - t!(cvt(libc::kill(-(cat.id() as libc::pid_t), libc::SIGINT))); + t!(cvt(libc::kill(-pgid, libc::SIGINT))); t!(cat.wait()); } @@ -184,9 +209,19 @@ fn test_setsid_no_posix_spawn() { cmd.pre_exec(Box::new(|| Ok(()))); // pre_exec forces fork + exec rather than posix spawn. let (mut cat, _pipes) = t!(cmd.spawn(Stdio::Null, true)); + let pid = cat.id() as libc::pid_t; + let pgid = libc::getpgid(pid); + assert_ne!(-1, pgid, "getpgid failed"); + assert_eq!(pid, pgid, "child process ID does not match its process group ID"); + + if cfg!(target_os = "qnx") { + // kill(-pgid) appears to be unable to terminate the child process on QNX8 + return; + } + // Setsid will create a new session and process group, so check that // we can kill the process group, which means there *is* one. - t!(cvt(libc::kill(-(cat.id() as libc::pid_t), libc::SIGINT))); + t!(cvt(libc::kill(-pgid, libc::SIGINT))); t!(cat.wait()); } diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs index 55906ed2854f2..51f6d21127f4f 100644 --- a/library/std/src/sys/process/unix/unix.rs +++ b/library/std/src/sys/process/unix/unix.rs @@ -193,10 +193,10 @@ impl Command { cvt(libc::fork()) } - // On QNX Neutrino, fork can fail with EBADF in case "another thread might have opened + // On QNX SDP, fork can fail with EBADF in case "another thread might have opened // or closed a file descriptor while the fork() was occurring". // Documentation says "... or try calling fork() again". This is what we do here. - // See also https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/f/fork.html + // See also https://www.qnx.com/developers/docs/7.1/com.qnx.doc.neutrino.lib_ref/topic/f/fork.html #[cfg(any(target_os = "nto", target_os = "qnx"))] unsafe fn do_fork(&mut self) -> Result { use crate::sys::io::errno; @@ -553,10 +553,10 @@ impl Command { } } - // On QNX Neutrino, posix_spawnp can fail with EBADF in case "another thread might have opened + // On QNX SDP, posix_spawnp can fail with EBADF in case "another thread might have opened // or closed a file descriptor while the posix_spawn() was occurring". // Documentation says "... or try calling posix_spawn() again". This is what we do here. - // See also http://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/p/posix_spawn.html + // See also https://www.qnx.com/developers/docs/7.1/com.qnx.doc.neutrino.lib_ref/topic/p/posix_spawn.html #[cfg(any(target_os = "nto", target_os = "qnx"))] unsafe fn retrying_libc_posix_spawnp( pid: *mut pid_t, @@ -756,7 +756,7 @@ impl Command { if self.get_setsid() { cfg_select! { all(target_os = "linux", target_env = "gnu") => { - flags |= libc::POSIX_SPAWN_SETSID; + flags |= libc::POSIX_SPAWN_SETSID as i32; } _ => { return Ok(None); diff --git a/library/std/src/sys/thread/unix.rs b/library/std/src/sys/thread/unix.rs index e52ffff165beb..2dbb0314cb271 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -278,7 +278,7 @@ pub fn available_parallelism() -> io::Result> { Ok(unsafe { NonZero::new_unchecked(cpus as usize) }) } - target_os = "nto" => { + any(target_os = "nto", target_os = "qnx") => { unsafe { use libc::_syspage_ptr; if _syspage_ptr.is_null() { @@ -347,7 +347,7 @@ pub fn current_os_id() -> Option { let id: libc::pid_t = unsafe { gettid() }; Some(id as u64) } - target_os = "nto" => { + any(target_os = "nto", target_os = "qnx") => { // SAFETY: FFI call with no preconditions. let id: libc::pid_t = unsafe { libc::gettid() }; Some(id as u64) @@ -392,6 +392,7 @@ pub fn current_os_id() -> Option { #[cfg(any( target_os = "linux", target_os = "nto", + target_os = "qnx", target_os = "solaris", target_os = "illumos", target_os = "vxworks", @@ -484,14 +485,14 @@ pub fn set_name(name: &CStr) { } } -#[cfg(any(target_os = "solaris", target_os = "illumos", target_os = "nto"))] +#[cfg(any(target_os = "solaris", target_os = "illumos", target_os = "nto", target_os = "qnx"))] pub fn set_name(name: &CStr) { weak!( fn pthread_setname_np(thread: libc::pthread_t, name: *const libc::c_char) -> libc::c_int; ); if let Some(f) = pthread_setname_np.get() { - #[cfg(target_os = "nto")] + #[cfg(any(target_os = "nto", target_os = "qnx"))] const THREAD_NAME_MAX: usize = libc::_NTO_THREAD_NAME_MAX as usize; #[cfg(any(target_os = "solaris", target_os = "illumos"))] const THREAD_NAME_MAX: usize = 32; diff --git a/library/std/src/sys/thread_local/key/racy.rs b/library/std/src/sys/thread_local/key/racy.rs index 0a10925ae6499..37ccb10337cf9 100644 --- a/library/std/src/sys/thread_local/key/racy.rs +++ b/library/std/src/sys/thread_local/key/racy.rs @@ -21,12 +21,12 @@ pub struct LazyKey { // Define a sentinel value that is likely not to be returned // as a TLS key. -#[cfg(not(target_os = "nto"))] +#[cfg(not(any(target_os = "nto", target_os = "qnx")))] const KEY_SENTVAL: usize = 0; -// On QNX Neutrino, 0 is always returned when currently not in use. -// Using 0 would mean to always create two keys and remote the first +// On QNX SDP, 0 is always returned when currently not in use. +// Using 0 would mean to always create two keys and remove the first // one (with value of 0) immediately afterwards. -#[cfg(target_os = "nto")] +#[cfg(any(target_os = "nto", target_os = "qnx"))] const KEY_SENTVAL: usize = libc::PTHREAD_KEYS_MAX + 1; impl LazyKey { diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 1bdbae13bdaec..3a1e12aad6c73 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -275,9 +275,9 @@ target | std | host | notes [`aarch64-unknown-linux-pauthtest`](platform-support/aarch64-unknown-linux-pauthtest.md) | ✓ | ✓ | ARM64 PAC ELF ABI [`aarch64-unknown-managarm-mlibc`](platform-support/managarm.md) | ? | | ARM64 Managarm [`aarch64-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | ARM64 NetBSD -[`aarch64-unknown-nto-qnx700`](platform-support/nto-qnx.md) | ? | | ARM64 QNX Neutrino 7.0 RTOS | -[`aarch64-unknown-nto-qnx710`](platform-support/nto-qnx.md) | ✓ | | ARM64 QNX Neutrino 7.1 RTOS with default network stack (io-pkt) | -[`aarch64-unknown-nto-qnx710_iosock`](platform-support/nto-qnx.md) | ✓ | | ARM64 QNX Neutrino 7.1 RTOS with new network stack (io-sock) | +[`aarch64-unknown-nto-qnx700`](platform-support/nto-qnx.md) | ? | | ARM64 QNX SDP 7.0 | +[`aarch64-unknown-nto-qnx710`](platform-support/nto-qnx.md) | ✓ | | ARM64 QNX SDP 7.1 with default network stack (io-pkt) | +[`aarch64-unknown-nto-qnx710_iosock`](platform-support/nto-qnx.md) | ✓ | | ARM64 QNX SDP 7.1 with new network stack (io-sock) | [`aarch64-unknown-qnx`](platform-support/nto-qnx.md) | ✓ | | ARM64 QNX SDP 8.0+ | [`aarch64-unknown-nuttx`](platform-support/nuttx.md) | ✓ | | ARM64 with NuttX [`aarch64-unknown-openbsd`](platform-support/openbsd.md) | ✓ | ✓ | ARM64 OpenBSD @@ -340,7 +340,7 @@ target | std | host | notes [`i586-unknown-redox`](platform-support/redox.md) | ✓ | | 32-bit x86 Redox OS (PentiumPro) [^x86_32-floats-x87] [`i686-apple-darwin`](platform-support/apple-darwin.md) | ✓ | ✓ | 32-bit macOS (10.12+, Sierra+, Penryn) [^x86_32-floats-return-ABI] [`i686-oe-linux-gnu`](platform-support/oe-linux-gnu.md) | ✓ | | 32-bit x86 OpenEmbedded/Yocto Linux (GNU) -[`i686-pc-nto-qnx700`](platform-support/nto-qnx.md) | * | | 32-bit x86 QNX Neutrino 7.0 RTOS (Pentium 4) [^x86_32-floats-return-ABI] +[`i686-pc-nto-qnx700`](platform-support/nto-qnx.md) | * | | 32-bit x86 QNX SDP 7.0 (Pentium 4) [^x86_32-floats-return-ABI] `i686-unknown-haiku` | ✓ | ✓ | 32-bit Haiku (Pentium 4) [^x86_32-floats-return-ABI] [`i686-unknown-helenos`](platform-support/helenos.md) | ✓ | | HelenOS IA-32 (see docs for pending issues) [`i686-unknown-hurd-gnu`](platform-support/hurd.md) | ✓ | ✓ | 32-bit GNU/Hurd (Pentium 4) [^x86_32-floats-return-ABI] @@ -449,8 +449,8 @@ target | std | host | notes [`x86_64-lynx-lynxos178`](platform-support/lynxos178.md) | | | x86_64 LynxOS-178 [`x86_64-oe-linux-gnu`](platform-support/oe-linux-gnu.md) | ✓ | | x86 64-bit OpenEmbedded/Yocto Linux (GNU) [`x86_64-pc-cygwin`](platform-support/x86_64-pc-cygwin.md) | ✓ | | 64-bit x86 Cygwin | -[`x86_64-pc-nto-qnx710`](platform-support/nto-qnx.md) | ✓ | | x86 64-bit QNX Neutrino 7.1 RTOS with default network stack (io-pkt) | -[`x86_64-pc-nto-qnx710_iosock`](platform-support/nto-qnx.md) | ✓ | | x86 64-bit QNX Neutrino 7.1 RTOS with new network stack (io-sock) | +[`x86_64-pc-nto-qnx710`](platform-support/nto-qnx.md) | ✓ | | x86 64-bit QNX SDP 7.1 with default network stack (io-pkt) | +[`x86_64-pc-nto-qnx710_iosock`](platform-support/nto-qnx.md) | ✓ | | x86 64-bit QNX SDP 7.1 with new network stack (io-sock) | [`x86_64-pc-qnx`](platform-support/nto-qnx.md) | ✓ | | x86 64-bit QNX SDP 8.0+ | [`x86_64-unikraft-linux-musl`](platform-support/unikraft-linux-musl.md) | ✓ | | 64-bit Unikraft with musl 1.2.5 `x86_64-unknown-dragonfly` | ✓ | ✓ | 64-bit DragonFlyBSD diff --git a/src/doc/rustc/src/platform-support/nto-qnx.md b/src/doc/rustc/src/platform-support/nto-qnx.md index b3ab1c7c4485b..899b550e3f603 100644 --- a/src/doc/rustc/src/platform-support/nto-qnx.md +++ b/src/doc/rustc/src/platform-support/nto-qnx.md @@ -83,49 +83,23 @@ For conditional compilation, the following QNX specific attributes are defined: ```toml profile = "compiler" change-id = 999999 - ``` - -2. Compile the Rust toolchain for an `x86_64-unknown-linux-gnu` host - - Compiling the Rust toolchain requires the same environment variables used for compiling C binaries. - Refer to the [QNX developer manual](https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.prog/topic/devel_OS_version.html). - - To compile for QNX, environment variables must be set to use the correct tools and compiler switches: - - `CC_=qcc` - - `CFLAGS_=` - - `CXX_=qcc` - - `AR_=` - - With: - - - `` target triplet using underscores instead of hyphens, e.g. `aarch64_unknown_nto_qnx710` - - `` - - - `-Vgcc_ntox86_cxx` for x86 (32 bit) - - `-Vgcc_ntox86_64_cxx` for x86_64 (64 bit) - - `-Vgcc_ntoaarch64le_cxx` for Aarch64 (64 bit) + [build] + host = ["x86_64-unknown-linux-gnu"] + target = ["x86_64-unknown-linux-gnu", "aarch64-unknown-nto-qnx710"] + ``` - - `` +2. Compile the Rust toolchain with the QNX SDP environment loaded - - `ntox86-ar` for x86 (32 bit) - - `ntox86_64-ar` for x86_64 (64 bit) - - `ntoaarch64-ar` for Aarch64 (64 bit) + As noted above, we need the right environment variables for QNX SDP to work, + and for `qcc` to be in your system PATH. Typically this is done by sourcing + the `qnxsdp-env.sh` file (or equivalent for your host platform). - Example to build the Rust toolchain including a standard library for x86_64-linux-gnu and Aarch64-QNX-7.1: + To build on Linux, you would run: ```bash - export build_env=' - CC_aarch64_unknown_nto_qnx710=qcc - CFLAGS_aarch64_unknown_nto_qnx710=-Vgcc_ntoaarch64le_cxx - CXX_aarch64_unknown_nto_qnx710=qcc - AR_aarch64_unknown_nto_qnx710=ntoaarch64-ar - ' - - env $build_env \ - ./x.py build \ - --target x86_64-unknown-linux-gnu,aarch64-unknown-nto-qnx710 \ - rustc library/core library/alloc library/std + source ~/qnx710/qnxsdp-env.sh + ./x.py build rustc library/core library/alloc library/std ``` ## Building Rust programs @@ -147,7 +121,7 @@ change calling conventions or memory layout. The test suites of the Rust compiler and standard library can be executed much like other Rust targets. The environment for testing should match the one used -during compiler compilation (refer to `build_env` and `qcc`/`PATH` above) with +during compiler compilation (refer to notes on `qnxsdp-env.sh` above) with the addition of the `TEST_DEVICE_ADDR` environment variable. The `TEST_DEVICE_ADDR` variable controls the remote runner and should point to a target running the `remote-test-server` executable. @@ -158,8 +132,8 @@ target maintainers which can be seen in the following example. To run all tests on a x86_64 QNX Neutrino 7.1 target: ```bash +source ~/qnx710/qnxsdp-env.sh export TEST_DEVICE_ADDR="1.2.3.4:12345" # must address the test target, can be a SSH tunnel -export build_env= # Disable tests that only work on the host or don't make sense for this target. # See also: @@ -174,11 +148,10 @@ export exclude_tests=' --exclude rustc --exclude rustdoc' -env $build_env \ - ./x.py test \ - $exclude_tests \ - --stage 1 \ - --target x86_64-pc-nto-qnx710 +./x.py test \ + $exclude_tests \ + --stage 1 \ + --target x86_64-pc-nto-qnx710 ``` ### Rust std library test suite diff --git a/src/tools/compiletest/src/directives/directive_names.rs b/src/tools/compiletest/src/directives/directive_names.rs index 7c4469afda282..70901f5b84c56 100644 --- a/src/tools/compiletest/src/directives/directive_names.rs +++ b/src/tools/compiletest/src/directives/directive_names.rs @@ -109,6 +109,7 @@ pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "ignore-pauthtest", "ignore-powerpc", "ignore-powerpc64", + "ignore-qnx", "ignore-remote", "ignore-riscv32", "ignore-riscv64", diff --git a/tests/codegen-llvm/thread-local.rs b/tests/codegen-llvm/thread-local.rs index 41df8c9be1b9d..8eb0443ebb9b5 100644 --- a/tests/codegen-llvm/thread-local.rs +++ b/tests/codegen-llvm/thread-local.rs @@ -5,6 +5,7 @@ //@ ignore-emscripten globals are used instead of thread locals //@ ignore-android does not use #[thread_local] //@ ignore-nto does not use #[thread_local] +//@ ignore-qnx does not use #[thread_local] #![crate_type = "lib"] diff --git a/tests/ui/abi/stack-probes-lto.rs b/tests/ui/abi/stack-probes-lto.rs index 6203be90590d3..94ba5a79aa725 100644 --- a/tests/ui/abi/stack-probes-lto.rs +++ b/tests/ui/abi/stack-probes-lto.rs @@ -8,7 +8,8 @@ //@ ignore-fuchsia no exception handler registered for segfault //@ compile-flags: -C lto //@ no-prefer-dynamic -//@ ignore-nto Crash analysis impossible at SIGSEGV in QNX Neutrino +//@ ignore-nto Crash analysis impossible at SIGSEGV in QNX SDP 7.x +//@ ignore-qnx Crash analysis impossible at SIGSEGV in QNX SDP 8.0+ //@ ignore-ios Stack probes are enabled, but the SIGSEGV handler isn't //@ ignore-tvos Stack probes are enabled, but the SIGSEGV handler isn't //@ ignore-watchos Stack probes are enabled, but the SIGSEGV handler isn't diff --git a/tests/ui/abi/stack-probes.rs b/tests/ui/abi/stack-probes.rs index 4d0301411e0fd..be36eeefc31ea 100644 --- a/tests/ui/abi/stack-probes.rs +++ b/tests/ui/abi/stack-probes.rs @@ -5,7 +5,8 @@ //@[x64] only-x86_64 //@ needs-subprocess //@ ignore-fuchsia no exception handler registered for segfault -//@ ignore-nto Crash analysis impossible at SIGSEGV in QNX Neutrino +//@ ignore-nto Crash analysis impossible at SIGSEGV in QNX SDP 7.x +//@ ignore-qnx Crash analysis impossible at SIGSEGV in QNX SDP 8.0+ //@ ignore-ios Stack probes are enabled, but the SIGSEGV handler isn't //@ ignore-tvos Stack probes are enabled, but the SIGSEGV handler isn't //@ ignore-watchos Stack probes are enabled, but the SIGSEGV handler isn't diff --git a/tests/ui/command/command-setgroups.rs b/tests/ui/command/command-setgroups.rs index 047f06729af98..6e369c9b56c04 100644 --- a/tests/ui/command/command-setgroups.rs +++ b/tests/ui/command/command-setgroups.rs @@ -2,6 +2,7 @@ //@ only-unix (this is a unix-specific test) //@ ignore-musl - returns dummy result for _SC_NGROUPS_MAX //@ ignore-nto - does not have `/bin/id`, expects groups to be i32 (not u32) +//@ ignore-qnx - does not have `/bin/id`, expects groups to be i32 (not u32) //@ needs-subprocess #![feature(rustc_private)] diff --git a/tests/ui/process/process-spawn-failure.rs b/tests/ui/process/process-spawn-failure.rs index ac2c34bc7836d..7f2dfa2aaa652 100644 --- a/tests/ui/process/process-spawn-failure.rs +++ b/tests/ui/process/process-spawn-failure.rs @@ -9,6 +9,7 @@ //@ ignore-vxworks no 'ps' //@ ignore-fuchsia no 'ps' //@ ignore-nto no 'ps' +//@ ignore-qnx no 'ps' //@ ignore-ios no 'ps' //@ ignore-tvos no 'ps' //@ ignore-watchos no 'ps' diff --git a/tests/ui/runtime/out-of-stack.rs b/tests/ui/runtime/out-of-stack.rs index 3e092728f2929..9b3878229417c 100644 --- a/tests/ui/runtime/out-of-stack.rs +++ b/tests/ui/runtime/out-of-stack.rs @@ -6,6 +6,7 @@ //@ needs-subprocess //@ ignore-fuchsia must translate zircon signal to SIGABRT, FIXME (#58590) //@ ignore-nto no stack overflow handler used (no alternate stack available) +//@ ignore-qnx no stack overflow handler used (no alternate stack available) //@ ignore-ios stack overflow handlers aren't enabled //@ ignore-tvos stack overflow handlers aren't enabled //@ ignore-watchos stack overflow handlers aren't enabled diff --git a/tests/ui/runtime/signal-alternate-stack-cleanup.rs b/tests/ui/runtime/signal-alternate-stack-cleanup.rs index 8d52e9e26a29c..73bf741a73f1c 100644 --- a/tests/ui/runtime/signal-alternate-stack-cleanup.rs +++ b/tests/ui/runtime/signal-alternate-stack-cleanup.rs @@ -8,6 +8,7 @@ //@ ignore-sgx no libc //@ ignore-vxworks no SIGWINCH in user space //@ ignore-nto no SA_ONSTACK +//@ ignore-qnx no SA_ONSTACK #![allow(function_casts_as_integer)] #![feature(rustc_private)] diff --git a/tests/ui/suggestions/missing-lifetime-specifier.rs b/tests/ui/suggestions/missing-lifetime-specifier.rs index 88d9736a071de..93416a9bc2ac7 100644 --- a/tests/ui/suggestions/missing-lifetime-specifier.rs +++ b/tests/ui/suggestions/missing-lifetime-specifier.rs @@ -4,6 +4,7 @@ //@ ignore-emscripten globals are used instead of thread locals //@ ignore-android does not use #[thread_local] //@ ignore-nto does not use #[thread_local] +//@ ignore-qnx does not use #[thread_local] // Different number of duplicated diagnostics on different targets //@ compile-flags: -Zdeduplicate-diagnostics=yes diff --git a/tests/ui/suggestions/missing-lifetime-specifier.stderr b/tests/ui/suggestions/missing-lifetime-specifier.stderr index b8c58155e636d..4c50f2a43e270 100644 --- a/tests/ui/suggestions/missing-lifetime-specifier.stderr +++ b/tests/ui/suggestions/missing-lifetime-specifier.stderr @@ -1,5 +1,5 @@ error[E0106]: missing lifetime specifiers - --> $DIR/missing-lifetime-specifier.rs:26:44 + --> $DIR/missing-lifetime-specifier.rs:27:44 | LL | static a: RefCell>>> = RefCell::new(HashMap::new()); | ^^^ expected 2 lifetime parameters @@ -11,7 +11,7 @@ LL | static a: RefCell>>>> = RefC | ++++++++++++++++++ error[E0106]: missing lifetime specifiers - --> $DIR/missing-lifetime-specifier.rs:30:44 + --> $DIR/missing-lifetime-specifier.rs:31:44 | LL | static b: RefCell>>> = RefCell::new(HashMap::new()); | ^ ^^^ expected 2 lifetime parameters @@ -25,7 +25,7 @@ LL | static b: RefCell $DIR/missing-lifetime-specifier.rs:34:47 + --> $DIR/missing-lifetime-specifier.rs:35:47 | LL | static c: RefCell>>>> = RefCell::new(HashMap::new()); | ^ expected 2 lifetime parameters @@ -37,7 +37,7 @@ LL | static c: RefCell>>>> = | +++++++++++++++++ error[E0106]: missing lifetime specifiers - --> $DIR/missing-lifetime-specifier.rs:38:44 + --> $DIR/missing-lifetime-specifier.rs:39:44 | LL | static d: RefCell>>>> = RefCell::new(HashMap::new()); | ^ ^ expected 2 lifetime parameters @@ -51,7 +51,7 @@ LL | static d: RefCell $DIR/missing-lifetime-specifier.rs:47:44 + --> $DIR/missing-lifetime-specifier.rs:48:44 | LL | static f: RefCell>>>> = | ^ expected named lifetime parameter @@ -68,7 +68,7 @@ LL + static f: RefCell>>>> = | error[E0107]: union takes 2 lifetime arguments but 1 lifetime argument was supplied - --> $DIR/missing-lifetime-specifier.rs:43:44 + --> $DIR/missing-lifetime-specifier.rs:44:44 | LL | static e: RefCell>>>> = RefCell::new(HashMap::new()); | ^^^ ------- supplied 1 lifetime argument @@ -76,7 +76,7 @@ LL | static e: RefCell>>>> = RefCell: | expected 2 lifetime arguments | note: union defined here, with 2 lifetime parameters: `'t`, `'k` - --> $DIR/missing-lifetime-specifier.rs:19:11 + --> $DIR/missing-lifetime-specifier.rs:20:11 | LL | pub union Qux<'t, 'k, I> { | ^^^ -- -- @@ -86,7 +86,7 @@ LL | static e: RefCell>>>> = | +++++++++ error[E0107]: trait takes 2 lifetime arguments but 1 lifetime argument was supplied - --> $DIR/missing-lifetime-specifier.rs:47:49 + --> $DIR/missing-lifetime-specifier.rs:48:49 | LL | static f: RefCell>>>> = | ^^^ ------- supplied 1 lifetime argument @@ -94,7 +94,7 @@ LL | static f: RefCell>>>> = | expected 2 lifetime arguments | note: trait defined here, with 2 lifetime parameters: `'t`, `'k` - --> $DIR/missing-lifetime-specifier.rs:23:7 + --> $DIR/missing-lifetime-specifier.rs:24:7 | LL | trait Tar<'t, 'k, I> {} | ^^^ -- --