From 47c9070de56d936b72f68914dfe7b7ebbffaf2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 7 Oct 2026 14:57:00 +0200 Subject: [PATCH 01/27] Parse move exprs as a bottom expr instead of a prefix expr --- compiler/rustc_parse/src/parser/expr.rs | 31 +++++++++---------- .../feature-gate-move_expr.stderr | 2 +- .../left-operand-of-dot-call-index-try.rs | 21 +++++++++++++ 3 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 tests/ui/move-expr/left-operand-of-dot-call-index-try.rs diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index bf99bbe26f087..5577c74850f41 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -410,12 +410,6 @@ impl<'a> Parser<'a> { pre_span, )); } - token::Ident(..) - if this.token.is_keyword(kw::Move) - && this.look_ahead(1, |t| *t == token::OpenParen) => - { - make_it!(this, attrs, |this, _| this.parse_expr_move(lo)) - } token::Ident(..) if this.may_recover() && this.is_mistaken_not_ident_negation() => { make_it!(this, attrs, |this, _| this.recover_not_expr(lo)) } @@ -447,16 +441,6 @@ impl<'a> Parser<'a> { self.parse_expr_unary(lo, UnOp::Not) } - fn parse_expr_move(&mut self, move_kw: Span) -> PResult<'a, (Span, ExprKind)> { - self.bump(); - self.psess.gated_spans.gate(sym::move_expr, move_kw); - self.expect(exp!(OpenParen))?; - let expr = self.parse_expr()?; - self.expect(exp!(CloseParen))?; - let span = move_kw.to(self.prev_token.span); - Ok((span, ExprKind::Move(expr, move_kw))) - } - fn is_mistaken_not_ident_negation(&self) -> bool { let token_cannot_continue_expr = |t: &Token| match t.uninterpolate().kind { // These tokens can start an expression after `!`, but @@ -1335,6 +1319,10 @@ impl<'a> Parser<'a> { this.parse_expr_builtin() } else if this.check_path() { this.parse_expr_path_start() + } else if this.token.is_keyword(kw::Move) + && this.look_ahead(1, |t| *t == token::OpenParen) + { + this.parse_expr_move() } else if this.check_keyword(exp!(Move)) || this.check_keyword(exp!(Use)) || this.check_keyword(exp!(Static)) @@ -2216,6 +2204,17 @@ impl<'a> Parser<'a> { Ok(self.mk_expr(blk.span, ExprKind::Block(blk, None))) } + fn parse_expr_move(&mut self) -> PResult<'a, Box> { + let move_kw = self.token.span; + self.expect_keyword(exp!(Move))?; + self.expect(exp!(OpenParen))?; + let expr = self.parse_expr()?; + self.expect(exp!(CloseParen))?; + let span = move_kw.to(self.prev_token.span); + self.psess.gated_spans.gate(sym::move_expr, span); + Ok(self.mk_expr(span, ExprKind::Move(expr, move_kw))) + } + /// Parses a closure expression (e.g., `move |args| expr`). fn parse_expr_closure(&mut self) -> PResult<'a, Box> { let lo = self.token.span; diff --git a/tests/ui/feature-gates/feature-gate-move_expr.stderr b/tests/ui/feature-gates/feature-gate-move_expr.stderr index 8b1da2c06893d..41a2ded842207 100644 --- a/tests/ui/feature-gates/feature-gate-move_expr.stderr +++ b/tests/ui/feature-gates/feature-gate-move_expr.stderr @@ -2,7 +2,7 @@ error[E0658]: `move(expr)` syntax is experimental --> $DIR/feature-gate-move_expr.rs:2:16 | LL | let _ = || move(2); - | ^^^^ + | ^^^^^^^ | = note: see issue #155050 for more information = help: add `#![feature(move_expr)]` to the crate attributes to enable diff --git a/tests/ui/move-expr/left-operand-of-dot-call-index-try.rs b/tests/ui/move-expr/left-operand-of-dot-call-index-try.rs new file mode 100644 index 0000000000000..700101a8a27f2 --- /dev/null +++ b/tests/ui/move-expr/left-operand-of-dot-call-index-try.rs @@ -0,0 +1,21 @@ +// Ensure that move exprs can be the left operand of dot, call, index & try ops. We once used to +// incorrectly parse move exprs as prefix exprs instead of bottom ones which used to prevent that. + +//@ check-pass +//@ edition: 2018.. +#![feature(move_expr)] + +fn main() { + let _ = async || { move(async {}).await }; // Await(Move(_)) + + let _ = || -> Result<(), ()> { + move(((),)).0; // Field(Move(_), ..) + move(Err(()))?; // Try(Move(_)) + move(drop)(()); // Call(Move(_), ..) + move([0])[0]; // Index(Move(_), ..) + + // Previously, we would parse this as Field(Return(Move(_)), ..) (`(return move(…)).0`) + // instead of Return(Field(Move(_), ..)) and thus fail with "no field `0` on type `!`". + return move((Ok(()),)).0; // OK! + }; +} From 604a21cab2edf48dc9b40e1a513273d52aafa167 Mon Sep 17 00:00:00 2001 From: Max Dexheimer Date: Fri, 25 Sep 2026 17:37:42 +0200 Subject: [PATCH 02/27] Document zero sized allocations in `Box` and `Vec` --- library/alloc/src/boxed.rs | 133 ++++++++++++++++---- library/alloc/src/string.rs | 4 + library/alloc/src/vec/mod.rs | 237 ++++++++++++----------------------- 3 files changed, 193 insertions(+), 181 deletions(-) diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index 7504763120532..9f3a17314c356 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -53,20 +53,26 @@ //! //! # Memory layout //! -//! For non-zero-sized values, a [`Box`] will use the [`Global`] allocator for its allocation. It is -//! valid to convert both ways between a [`Box`] and a raw pointer allocated with the [`Global`] -//! allocator, given that the [`Layout`] used with the allocator is correct for the type and the raw -//! pointer points to a valid value of the right type. More precisely, a `value: *mut T` that has -//! been allocated with the [`Global`] allocator with `Layout::for_value(&*value)` may be converted -//! into a box using [`Box::::from_raw(value)`]. Conversely, the memory backing a `value: *mut T` -//! obtained from [`Box::::into_raw`] may be deallocated using the [`Global`] allocator with -//! [`Layout::for_value(&*value)`]. +//! For non-zero-sized values, a [`Box`] will use its allocator of type `A` for its allocation. +//! It is valid to convert both ways between a `Box` and a raw pointer allocated with `A`, so long as: +//! - the size of the pointee, as reported by [`size_of_val`], is non-zero, +//! - the [`Layout::for_value`] of the pointee [*fits*](Allocator#memory-fitting) the memory block, and +//! - the pointee is a valid instance of `T` (for the given metadata of the pointer). //! -//! For zero-sized values, the `Box` pointer has to be non-null and sufficiently aligned. The -//! recommended way to build a Box to a ZST if `Box::new` cannot be used is to use -//! [`ptr::NonNull::dangling`]. +//! For zero-sized values, a [`Box`] may use any sufficiently aligned non-null pointer. +//! While it is sound to convert a zero-sized allocation from `A` into a `Box`, as +//! long as it is backed by a valid value of `T`, doing so may lead to memory leaks, as +//! the destructor of [`Box`] will not call [`deallocate`](Allocator::deallocate) +//! on zero-sized values. It is recommended to use [`ptr::dangling`] instead. //! -//! On top of these basic layout requirements, a `Box` must point to a valid value of `T`. +//! Likewise, manually deallocating the raw pointer acquired from a `Box` is only sound +//! if the size of the pointee is non-zero, as zero-sized values may be represented by +//! a pointer that is not [*currently allocated*] by that allocator. It is recommended to +//! pair [`Box::into_raw_with_allocator`] with [`Box::from_raw_in`], rather than +//! doing this. +//! +//! Note that even the [`Global`] allocator might not use dangling pointers for +//! zero-sized allocations, even though *most* implementations do this. //! //! So long as `T: Sized`, a `Box` is guaranteed to be represented //! as a single pointer and is also ABI-compatible with C pointers @@ -172,6 +178,7 @@ //! and it's best to avoid relying on this edition-dependent behavior if you wish to preserve //! compatibility with future versions of the compiler. //! +//! [*currently allocated*]: Allocator#currently-allocated-memory //! [ucg#198]: https://github.com/rust-lang/unsafe-code-guidelines/issues/198 //! [ucg#326]: https://github.com/rust-lang/unsafe-code-guidelines/issues/326 //! [dereferencing]: core::ops::Deref @@ -275,12 +282,15 @@ impl Box { /// Allocates memory on the heap and then places `x` into it. /// /// This doesn't actually allocate if `T` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// /// ``` /// let five = Box::new(5); /// ``` + /// + /// [memory layout]: self#memory-layout #[cfg(not(no_global_oom_handling))] #[inline(always)] #[stable(feature = "rust1", since = "1.0.0")] @@ -367,6 +377,7 @@ impl Box { /// returning an error if the allocation fails /// /// This doesn't actually allocate if `T` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -376,6 +387,8 @@ impl Box { /// let five = Box::try_new(5)?; /// # Ok::<(), std::alloc::AllocError>(()) /// ``` + /// + /// [memory layout]: self#memory-layout #[unstable(feature = "allocator_ext", issue = "163177", implied_by = "allocator_api")] #[inline] pub fn try_new(x: T) -> Result { @@ -434,6 +447,7 @@ impl Box { /// Allocates memory in the given allocator then places `x` into it. /// /// This doesn't actually allocate if `T` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -442,6 +456,8 @@ impl Box { /// /// let five = Box::new_in(5, System); /// ``` + /// + /// [memory layout]: self#memory-layout #[cfg(not(no_global_oom_handling))] #[stable(feature = "allocator_api", since = "CURRENT_RUSTC_VERSION")] #[must_use] @@ -457,6 +473,7 @@ impl Box { /// returning an error if the allocation fails /// /// This doesn't actually allocate if `T` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -468,6 +485,8 @@ impl Box { /// let five = Box::try_new_in(5, System)?; /// # Ok::<(), std::alloc::AllocError>(()) /// ``` + /// + /// [memory layout]: self#memory-layout #[unstable(feature = "allocator_ext", issue = "163177", implied_by = "allocator_api")] #[inline] pub fn try_new_in(x: T, alloc: A) -> Result { @@ -774,6 +793,7 @@ impl Box { /// Allocates memory on the heap then clones `src` into it. /// /// This doesn't actually allocate if `src` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -782,6 +802,8 @@ impl Box { /// /// let hello: Box = Box::clone_from_ref("hello"); /// ``` + /// + /// [memory layout]: self#memory-layout #[cfg(not(no_global_oom_handling))] #[unstable(feature = "clone_from_ref", issue = "149075")] #[must_use] @@ -793,6 +815,7 @@ impl Box { /// Allocates memory on the heap then clones `src` into it, returning an error if allocation fails. /// /// This doesn't actually allocate if `src` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -802,6 +825,8 @@ impl Box { /// let hello: Box = Box::try_clone_from_ref("hello")?; /// # Ok::<(), std::alloc::AllocError>(()) /// ``` + /// + /// [memory layout]: self#memory-layout #[unstable(feature = "clone_from_ref", issue = "149075")] //#[unstable(feature = "allocator_ext", issue = "163177", implied_by = "allocator_api")] #[inline] @@ -814,6 +839,7 @@ impl Box { /// Allocates memory in the given allocator then clones `src` into it. /// /// This doesn't actually allocate if `src` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -824,6 +850,8 @@ impl Box { /// /// let hello: Box = Box::clone_from_ref_in("hello", System); /// ``` + /// + /// [memory layout]: self#memory-layout #[cfg(not(no_global_oom_handling))] #[unstable(feature = "clone_from_ref", issue = "149075")] //#[unstable(feature = "allocator_ext", issue = "163177", implied_by = "allocator_api")] @@ -840,6 +868,7 @@ impl Box { /// Allocates memory in the given allocator then clones `src` into it, returning an error if allocation fails. /// /// This doesn't actually allocate if `src` is zero-sized. + /// See the [memory layout] section for more details. /// /// # Examples /// @@ -851,6 +880,8 @@ impl Box { /// let hello: Box = Box::try_clone_from_ref_in("hello", System)?; /// # Ok::<(), std::alloc::AllocError>(()) /// ``` + /// + /// [memory layout]: self#memory-layout #[unstable(feature = "clone_from_ref", issue = "149075")] //#[unstable(feature = "allocator_ext", issue = "163177", implied_by = "allocator_api")] #[inline] @@ -1300,9 +1331,16 @@ impl Box { /// memory problems. For example, a double-free may occur if the /// function is called twice on the same raw pointer. /// - /// The raw pointer must point to a block of memory allocated by the global allocator. + /// The exact safety requirements are described in the [memory layout] section, together + /// with recommendations about how to handle the special case of zero-sized allocations + /// correctly. + /// + /// In particular, if the size of the pointee, as reported by [`size_of_val`], is + /// nonzero, then the raw pointer must point to a block of memory allocated by + /// [`Global`]. + /// If the size of the pointee is zero, then the raw pointer can be any + /// sufficiently aligned non-null pointer, e.g. [`ptr::dangling`]. /// - /// The safety conditions are described in the [memory layout] section. /// Note that the [considerations for unsafe code] apply to all `Box` values. /// /// # Examples @@ -1352,9 +1390,16 @@ impl Box { /// memory problems. For example, a double-free may occur if the /// function is called twice on the same `NonNull` pointer. /// - /// The non-null pointer must point to a block of memory allocated by the global allocator. + /// The exact safety requirements are described in the [memory layout] section, together + /// with recommendations about how to handle the special case of zero-sized allocations + /// correctly. + /// + /// In particular, if the size of the pointee, as reported by [`size_of_val`], is + /// nonzero, then the raw pointer must point to a block of memory allocated by + /// [`Global`]. + /// If the size of the pointee is zero, then the raw pointer can be any + /// sufficiently aligned pointer, e.g. [`NonNull::dangling`]. /// - /// The safety conditions are described in the [memory layout] section. /// Note that the [considerations for unsafe code] apply to all `Box` values. /// /// # Examples @@ -1403,6 +1448,11 @@ impl Box { /// [`Box::from_raw`] function, allowing the `Box` destructor to perform /// the cleanup. /// + /// While manually deallocating the pointer through [`deallocate`] + /// is possible, you must only do this if the pointee's size, as reported by + /// [`size_of_val`], is non-zero (the `Box` may be a dangling pointer + /// otherwise). See the [memory layout] section for more details. + /// /// Note: this is an associated function, which means that you have /// to call it as `Box::into_raw(b)` instead of `b.into_raw()`. This /// is so that there is no conflict with a method on the inner type. @@ -1438,6 +1488,7 @@ impl Box { /// ``` /// /// [memory layout]: self#memory-layout + /// [`deallocate`]: Allocator::deallocate #[must_use = "losing the pointer will leak memory"] #[stable(feature = "box_raw", since = "1.4.0")] #[inline] @@ -1465,6 +1516,11 @@ impl Box { /// [`Box::from_non_null`] function, allowing the `Box` destructor to /// perform the cleanup. /// + /// While manually deallocating the pointer through [`deallocate`] + /// is possible, you must only do this if the pointee's size, as reported by + /// [`size_of_val`], is non-zero (the `Box` may be a dangling pointer + /// otherwise). See the [memory layout] section for more details. + /// /// Note: this is an associated function, which means that you have /// to call it as `Box::into_non_null(b)` instead of `b.into_non_null()`. /// This is so that there is no conflict with a method on the inner type. @@ -1499,6 +1555,7 @@ impl Box { /// ``` /// /// [memory layout]: self#memory-layout + /// [`deallocate`]: Allocator::deallocate #[must_use = "losing the pointer will leak memory"] #[stable(feature = "box_vec_non_null", since = "1.99.0")] #[inline] @@ -1517,9 +1574,8 @@ impl Box { /// /// After calling this function, the raw pointer is owned by the /// resulting `Box`. Specifically, the `Box` destructor will call - /// the destructor of `T` and free the allocated memory. For this - /// to be safe, the memory must have been allocated in accordance - /// with the [memory layout] used by `Box` . + /// the destructor of `T` and free the allocated memory, except + /// in the cases described in the [memory layout] section. /// /// # Safety /// @@ -1527,9 +1583,16 @@ impl Box { /// memory problems. For example, a double-free may occur if the /// function is called twice on the same raw pointer. /// - /// The raw pointer must point to a block of memory allocated by `alloc`. + /// The exact safety requirements are described in the [memory layout] section, together + /// with recommendations about how to handle the special case of zero-sized allocations + /// correctly. + /// + /// In particular, if the size of the pointee, as reported by [`size_of_val`], is + /// nonzero, then the raw pointer must point to a block of memory allocated by + /// `alloc`. + /// If the size of the pointee is zero, then the raw pointer can be any + /// sufficiently aligned non-null pointer, e.g. [`ptr::dangling`]. /// - /// The safety conditions are described in the [memory layout] section. /// Note that the [considerations for unsafe code] apply to all `Box` values. /// /// # Examples @@ -1573,9 +1636,8 @@ impl Box { /// /// After calling this function, the `NonNull` pointer is owned by /// the resulting `Box`. Specifically, the `Box` destructor will call - /// the destructor of `T` and free the allocated memory. For this - /// to be safe, the memory must have been allocated in accordance - /// with the [memory layout] used by `Box` . + /// the destructor of `T` and free the allocated memory, except + /// in the cases described in the [memory layout] section. /// /// # Safety /// @@ -1583,9 +1645,16 @@ impl Box { /// memory problems. For example, a double-free may occur if the /// function is called twice on the same raw pointer. /// - /// The non-null pointer must point to a block of memory allocated by `alloc`. + /// The exact safety requirements are described in the [memory layout] section, together + /// with recommendations about how to handle the special case of zero-sized allocations + /// correctly. + /// + /// In particular, if the size of the pointee, as reported by [`size_of_val`], is + /// nonzero, then the raw pointer must point to a block of memory allocated by + /// `alloc`. + /// If the size of the pointee is zero, then the raw pointer can be any + /// sufficiently aligned pointer, e.g. [`NonNull::dangling`]. /// - /// The safety conditions are described in the [memory layout] section. /// Note that the [considerations for unsafe code] apply to all `Box` values. /// /// # Examples @@ -1634,6 +1703,11 @@ impl Box { /// [`Box::from_raw_in`] function, allowing the `Box` destructor to perform /// the cleanup. /// + /// While manually deallocating the pointer through [`deallocate`] + /// is possible, you must only do this if the pointee's size, as reported by + /// [`size_of_val`], is non-zero (the `Box` may be a dangling pointer + /// otherwise). See the [memory layout] section for more details. + /// /// Note: this is an associated function, which means that you have /// to call it as `Box::into_raw_with_allocator(b)` instead of `b.into_raw_with_allocator()`. This /// is so that there is no conflict with a method on the inner type. @@ -1664,6 +1738,7 @@ impl Box { /// ``` /// /// [memory layout]: self#memory-layout + /// [`deallocate`]: Allocator::deallocate #[must_use = "losing the pointer will leak memory"] #[stable(feature = "allocator_api", since = "CURRENT_RUSTC_VERSION")] #[rustc_const_unstable(feature = "const_heap", issue = "79597")] @@ -1693,6 +1768,11 @@ impl Box { /// [`Box::from_non_null_in`] function, allowing the `Box` destructor to /// perform the cleanup. /// + /// While manually deallocating the pointer through [`deallocate`] + /// is possible, you must only do this if the pointee's size, as reported by + /// [`size_of_val`], is non-zero (the `Box` may be a dangling pointer + /// otherwise). See the [memory layout] section for more details. + /// /// Note: this is an associated function, which means that you have /// to call it as `Box::into_non_null_with_allocator(b)` instead of /// `b.into_non_null_with_allocator()`. This is so that there is no @@ -1722,6 +1802,7 @@ impl Box { /// ``` /// /// [memory layout]: self#memory-layout + /// [`deallocate`]: Allocator::deallocate #[must_use = "losing the pointer will leak memory"] #[stable(feature = "allocator_api", since = "CURRENT_RUSTC_VERSION")] #[inline] diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 03c07b15712bb..1d864b061a825 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -940,6 +940,10 @@ impl String { /// into a `String` with the [`from_raw_parts`] function, allowing /// the destructor to perform the cleanup. /// + /// If `capacity == 0`, then `pointer` may be dangling. Otherwise, + /// it will always be valid for reads and writes over `capacity` + /// bytes, and point to the start of the string. + /// /// [`from_raw_parts`]: String::from_raw_parts /// /// # Examples diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index f837d963a572e..6970235e65e83 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -51,25 +51,50 @@ //! //! # Memory layout //! -//! When the type is non-zero-sized and the capacity is nonzero, [`Vec`] uses the [`Global`] -//! allocator for its allocation. It is valid to convert both ways between such a [`Vec`] and a raw -//! pointer allocated with the [`Global`] allocator, provided that the [`Layout`] used with the -//! allocator is correct for a sequence of `capacity` elements of the type, and the first `len` -//! values pointed to by the raw pointer are valid. More precisely, a `ptr: *mut T` that has been -//! allocated with the [`Global`] allocator with [`Layout::array::(capacity)`][Layout::array] may -//! be converted into a vec using -//! [`Vec::::from_raw_parts(ptr, len, capacity)`](Vec::from_raw_parts). Conversely, the memory -//! backing a `value: *mut T` obtained from [`Vec::::as_mut_ptr`] may be deallocated using the -//! [`Global`] allocator with the same layout. +//! A [`Vec`] conceptually consists of a raw pointer `ptr` to the start of the allocation, a +//! length `len`, a capacity `cap`, and an allocator `alloc`. +//! While the exact fields and their order are not specified, a [`Vec`] can be converted to and +//! from these parts using [`Vec::into_raw_parts_with_allocator`], [`Vec::from_raw_parts_in`], +//! [`Vec::into_raw_parts`], and [`Vec::from_raw_parts`], as well as their [`NonNull`]-based +//! versions. //! -//! For zero-sized types (ZSTs), or when the capacity is zero, the `Vec` pointer must be non-null -//! and sufficiently aligned. The recommended way to build a `Vec` of ZSTs if [`vec!`] cannot be -//! used is to use [`ptr::NonNull::dangling`]. +//! The parts of a [`Vec`] have the following invariants: //! +//! * If [`size_of::()`](size_of) and `cap` are non-zero, then: +//! - `ptr` must point to the start of a memory block [*currently allocated*] via `alloc`. +//! - [`Layout::array::(cap)`] must [*fit*] the memory block of `ptr`. In particular, +//! * `T` must have the same alignment as the [`Layout`] `ptr` was allocated with, and +//! * `size_of::() * cap` must be at least the size of the [`Layout`] `ptr` was +//! allocated with, and at most the size of the allocation returned the allocator. +//! - `size_of::() * cap` must be at most [`isize::MAX`]. +//! See the safety documentation of [`pointer::offset`]. +//! * If [`size_of::()`](size_of) or `cap` is zero, then `ptr` must be any sufficiently aligned non-null pointer. +//! +//! NOTE: `ptr` will not be passed to the allocator for deallocation by the destructor of [`Vec`] +//! in this case. Thus, it is unwise to use a pointer acquired from the allocator for this, as +//! overallocated memory may end up being leaked. Use [`ptr::dangling`] instead. +//! * The following must always be satisfied: +//! - `len` needs to be less than or equal to `cap`. +//! - At `ptr`, there are at least `len` valid values of type `T`, laid out contiguously +//! (like an array). These values are considered owned by the [`Vec`]. +//! +//! It is sound to create a [`Vec`] from parts that satisfy these invariants. +//! +//! Conversely, the memory backing `ptr` obtained from [`Vec::as_mut_ptr`] or +//! [`Vec::into_raw_parts_with_allocator`]-like methods may be deallocated +//! using the allocator, with layout [`Layout::array::(cap)`], provided +//! that [`size_of::()`](size_of) or `cap` is non-zero. +//! To automatically handle the case where the allocation is zero-sized, +//! it is recommended to use [`Vec::from_raw_parts_in`], instead of manually +//! deallocating. +//! +//! [*fit*]: crate::alloc::Allocator#memory-fitting +//! [*currently allocated*]: crate::alloc::Allocator#currently-allocated-memory +//! [`allocate`]: crate::alloc::Allocator::allocate //! [`push`]: Vec::push //! [`ptr::NonNull::dangling`]: NonNull::dangling //! [`Layout`]: crate::alloc::Layout -//! [Layout::array]: crate::alloc::Layout::array +//! [`Layout::array::(cap)`]: crate::alloc::Layout::array #![stable(feature = "rust1", since = "1.0.0")] @@ -303,35 +328,13 @@ mod sve_retain; /// /// # Guarantees /// +/// The following section is to be read as an addendum of the +/// [memory layout](self#memory-layout) section. +/// /// Due to its incredibly fundamental nature, `Vec` makes a lot of guarantees /// about its design. This ensures that it's as low-overhead as possible in /// the general case, and can be correctly manipulated in primitive ways -/// by unsafe code. Note that these guarantees refer to an unqualified `Vec`. -/// If additional type parameters are added (e.g., to support custom allocators), -/// overriding their defaults may change the behavior. -/// -/// Most fundamentally, `Vec` is and always will be a (pointer, capacity, length) -/// triplet. No more, no less. The order of these fields is completely -/// unspecified, and you should use the appropriate methods to modify these. -/// The pointer will never be null, so this type is null-pointer-optimized. -/// -/// However, the pointer might not actually point to allocated memory. In particular, -/// if you construct a `Vec` with capacity 0 via [`Vec::new`], [`vec![]`][`vec!`], -/// [`Vec::with_capacity(0)`][`Vec::with_capacity`], or by calling [`shrink_to_fit`] -/// on an empty Vec, it will not allocate memory. Similarly, if you store zero-sized -/// types inside a `Vec`, it will not allocate space for them. *Note that in this case -/// the `Vec` might not report a [`capacity`] of 0*. `Vec` will allocate if and only -/// if [size_of::\]\() * [capacity]\() > 0. In general, `Vec`'s allocation -/// details are very subtle --- if you intend to allocate memory using a `Vec` -/// and use it for something else (either to pass to unsafe code, or to build your -/// own memory-backed collection), be sure to deallocate this memory by using -/// `from_raw_parts` to recover the `Vec` and then dropping it. -/// -/// If a `Vec` *has* allocated memory, then the memory it points to is on the heap -/// (as defined by the allocator Rust is configured to use by default), and its -/// pointer points to [`len`] initialized, contiguous elements in order (what -/// you would see if you coerced it to a slice), followed by [capacity] - [len] -/// logically uninitialized, contiguous elements. +/// by unsafe code. /// /// A vector containing the elements `'a'` and `'b'` with capacity 4 can be /// visualized as below. The top part is the `Vec` struct, it contains a @@ -472,7 +475,7 @@ impl Vec { /// /// The vector will be able to hold at least `capacity` elements without /// reallocating. This method is allowed to allocate for more elements than - /// `capacity`. If `capacity` is zero, the vector will not allocate. + /// `capacity`. If `capacity` is zero, the vector [will not allocate]. /// /// It is important to note that although the returned vector has the /// minimum *capacity* specified, the vector will have a zero *length*. For @@ -482,11 +485,12 @@ impl Vec { /// If it is important to know the exact allocated capacity of a `Vec`, /// always use the [`capacity`] method after construction. /// - /// For `Vec` where `T` is a zero-sized type, there will be no allocation - /// and the capacity will always be `usize::MAX`. + /// For `Vec` where `T` is a zero-sized type, the vector + /// [will not allocate], and the capacity will always be `usize::MAX`. /// /// [Capacity and reallocation]: #capacity-and-reallocation /// [`capacity`]: Vec::capacity + /// [will not allocate]: self#memory-layout /// /// # Panics /// @@ -532,12 +536,14 @@ impl Vec { /// /// The vector will be able to hold at least `capacity` elements without /// reallocating. This method is allowed to allocate for more elements than - /// `capacity`. If `capacity` is zero, the vector will not allocate. + /// `capacity`. If `capacity` is zero, the vector [will not allocate]. /// /// # Errors /// /// Returns an error if the capacity exceeds `isize::MAX` _bytes_, /// or if the allocator reports allocation failure. + /// + /// [will not allocate]: self#memory-layout #[inline] #[unstable(feature = "try_with_capacity", issue = "91913")] pub fn try_with_capacity(capacity: usize) -> Result { @@ -548,34 +554,10 @@ impl Vec { /// /// # Safety /// - /// This is highly unsafe, due to the number of invariants that aren't - /// checked: - /// - /// * If `T` is not a zero-sized type and the capacity is nonzero, `ptr` must have - /// been allocated using the global allocator, such as via the [`alloc::alloc`] - /// function. If `T` is a zero-sized type or the capacity is zero, `ptr` need - /// only be non-null and aligned. - /// * `T` needs to have the same alignment as what `ptr` was allocated with, - /// if the pointer is required to be allocated. - /// (`T` having a less strict alignment is not sufficient, the alignment really - /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be - /// allocated and deallocated with the same layout.) - /// * The size of `T` times the `capacity` (i.e. the allocated size in bytes), if - /// nonzero, needs to be the same size as the pointer was allocated with. - /// (Because similar to alignment, [`dealloc`] must be called with the same - /// layout `size`.) - /// * `length` needs to be less than or equal to `capacity`. - /// * The first `length` values must be properly initialized values of type `T`. - /// * `capacity` needs to be the capacity that the pointer was allocated with, - /// if the pointer is required to be allocated. - /// * The allocated size in bytes must be no larger than `isize::MAX`. - /// See the safety documentation of [`pointer::offset`]. - /// - /// These requirements are always upheld by any `ptr` that has been allocated - /// via `Vec`. Other allocation sources are allowed if the invariants are - /// upheld. - /// - /// Violating these may cause problems like corrupting the allocator's + /// The parts must satisfy the invariants described in the [memory layout] section, + /// with allocator [`Global`]. + /// + /// Violating them may cause problems like corrupting the allocator's /// internal data structures. For example it is normally **not** safe /// to build a `Vec` from a pointer to a C `char` array with length /// `size_t`, doing so is only safe if the array was initially allocated by @@ -594,8 +576,7 @@ impl Vec { /// function. /// /// [`String`]: crate::string::String - /// [`alloc::alloc`]: crate::alloc::alloc - /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc + /// [memory layout]: self#memory-layout /// /// # Examples /// @@ -655,29 +636,10 @@ impl Vec { /// /// # Safety /// - /// This is highly unsafe, due to the number of invariants that aren't - /// checked: - /// - /// * `ptr` must have been allocated using the global allocator, such as via - /// the [`alloc::alloc`] function. - /// * `T` needs to have the same alignment as what `ptr` was allocated with. - /// (`T` having a less strict alignment is not sufficient, the alignment really - /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be - /// allocated and deallocated with the same layout.) - /// * The size of `T` times the `capacity` (i.e. the allocated size in bytes) needs - /// to be the same size as the pointer was allocated with. (Because similar to - /// alignment, [`dealloc`] must be called with the same layout `size`.) - /// * `length` needs to be less than or equal to `capacity`. - /// * The first `length` values must be properly initialized values of type `T`. - /// * `capacity` needs to be the capacity that the pointer was allocated with. - /// * The allocated size in bytes must be no larger than `isize::MAX`. - /// See the safety documentation of [`pointer::offset`]. - /// - /// These requirements are always upheld by any `ptr` that has been allocated - /// via `Vec`. Other allocation sources are allowed if the invariants are - /// upheld. - /// - /// Violating these may cause problems like corrupting the allocator's + /// The parts must satisfy the invariants described in the [memory layout] section, + /// with allocator [`Global`]. + /// + /// Violating them may cause problems like corrupting the allocator's /// internal data structures. For example it is normally **not** safe /// to build a `Vec` from a pointer to a C `char` array with length /// `size_t`, doing so is only safe if the array was initially allocated by @@ -696,8 +658,7 @@ impl Vec { /// function. /// /// [`String`]: crate::string::String - /// [`alloc::alloc`]: crate::alloc::alloc - /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc + /// [memory layout]: self#memory-layout /// /// # Examples /// @@ -920,7 +881,7 @@ const impl Vec { /// /// The vector will be able to hold at least `capacity` elements without /// reallocating. This method is allowed to allocate for more elements than - /// `capacity`. If `capacity` is zero, the vector will not allocate. + /// `capacity`. If `capacity` is zero, the vector [will not allocate]. /// /// It is important to note that although the returned vector has the /// minimum *capacity* specified, the vector will have a zero *length*. For @@ -930,11 +891,12 @@ const impl Vec { /// If it is important to know the exact allocated capacity of a `Vec`, /// always use the [`capacity`] method after construction. /// - /// For `Vec` where `T` is a zero-sized type, there will be no allocation - /// and the capacity will always be `usize::MAX`. + /// For `Vec` where `T` is a zero-sized type, the vector + /// [will not allocate], and the capacity will always be `usize::MAX`. /// /// [Capacity and reallocation]: #capacity-and-reallocation /// [`capacity`]: Vec::capacity + /// [will not allocate]: self#memory-layout /// /// # Panics /// @@ -1072,12 +1034,14 @@ impl Vec { /// /// The vector will be able to hold at least `capacity` elements without /// reallocating. This method is allowed to allocate for more elements than - /// `capacity`. If `capacity` is zero, the vector will not allocate. + /// `capacity`. If `capacity` is zero, the vector [will not allocate]. /// /// # Errors /// /// Returns an error if the capacity exceeds `isize::MAX` _bytes_, /// or if the allocator reports allocation failure. + /// + /// [will not allocate]: self#memory-layout #[inline] #[unstable(feature = "allocator_ext", issue = "163177", implied_by = "allocator_api")] // #[unstable(feature = "try_with_capacity", issue = "91913")] @@ -1090,28 +1054,9 @@ impl Vec { /// /// # Safety /// - /// This is highly unsafe, due to the number of invariants that aren't - /// checked: - /// - /// * `ptr` must be [*currently allocated*] via the given allocator `alloc`. - /// * `T` needs to have the same alignment as what `ptr` was allocated with. - /// (`T` having a less strict alignment is not sufficient, the alignment really - /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be - /// allocated and deallocated with the same layout.) - /// * The size of `T` times the `capacity` (i.e. the allocated size in bytes) needs - /// to be the same size as the pointer was allocated with. (Because similar to - /// alignment, [`dealloc`] must be called with the same layout `size`.) - /// * `length` needs to be less than or equal to `capacity`. - /// * The first `length` values must be properly initialized values of type `T`. - /// * `capacity` needs to [*fit*] the layout size that the pointer was allocated with. - /// * The allocated size in bytes must be no larger than `isize::MAX`. - /// See the safety documentation of [`pointer::offset`]. - /// - /// These requirements are always upheld by any `ptr` that has been allocated - /// via `Vec`. Other allocation sources are allowed if the invariants are - /// upheld. - /// - /// Violating these may cause problems like corrupting the allocator's + /// The parts must satisfy the invariants described in the [memory layout] section. + /// + /// Violating them may cause problems like corrupting the allocator's /// internal data structures. For example it is **not** safe /// to build a `Vec` from a pointer to a C `char` array with length `size_t`. /// It's also not safe to build one from a `Vec` and its length, because @@ -1126,9 +1071,8 @@ impl Vec { /// function. /// /// [`String`]: crate::string::String - /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc - /// [*currently allocated*]: crate::alloc::Allocator#currently-allocated-memory - /// [*fit*]: crate::alloc::Allocator#memory-fitting + /// [`allocate`]: crate::alloc::Allocator::allocate + /// [memory layout]: self#memory-layout /// /// # Examples /// @@ -1204,28 +1148,9 @@ impl Vec { /// /// # Safety /// - /// This is highly unsafe, due to the number of invariants that aren't - /// checked: - /// - /// * `ptr` must be [*currently allocated*] via the given allocator `alloc`. - /// * `T` needs to have the same alignment as what `ptr` was allocated with. - /// (`T` having a less strict alignment is not sufficient, the alignment really - /// needs to be equal to satisfy the [`dealloc`] requirement that memory must be - /// allocated and deallocated with the same layout.) - /// * The size of `T` times the `capacity` (i.e. the allocated size in bytes) needs - /// to be the same size as the pointer was allocated with. (Because similar to - /// alignment, [`dealloc`] must be called with the same layout `size`.) - /// * `length` needs to be less than or equal to `capacity`. - /// * The first `length` values must be properly initialized values of type `T`. - /// * `capacity` needs to [*fit*] the layout size that the pointer was allocated with. - /// * The allocated size in bytes must be no larger than `isize::MAX`. - /// See the safety documentation of [`pointer::offset`]. - /// - /// These requirements are always upheld by any `ptr` that has been allocated - /// via `Vec`. Other allocation sources are allowed if the invariants are - /// upheld. - /// - /// Violating these may cause problems like corrupting the allocator's + /// The parts must satisfy the invariants described in the [memory layout] section. + /// + /// Violating them may cause problems like corrupting the allocator's /// internal data structures. For example it is **not** safe /// to build a `Vec` from a pointer to a C `char` array with length `size_t`. /// It's also not safe to build one from a `Vec` and its length, because @@ -1241,8 +1166,7 @@ impl Vec { /// /// [`String`]: crate::string::String /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc - /// [*currently allocated*]: crate::alloc::Allocator#currently-allocated-memory - /// [*fit*]: crate::alloc::Allocator#memory-fitting + /// [memory layout]: self#memory-layout /// /// # Examples /// @@ -1902,7 +1826,7 @@ impl Vec { } /// Returns a raw pointer to the vector's buffer, or a dangling raw pointer - /// valid for zero sized reads if the vector didn't allocate. + /// valid for zero sized reads if the vector [didn't allocate]. /// /// The caller must ensure that the vector outlives the pointer this /// function returns, or else it will end up dangling. @@ -1954,6 +1878,7 @@ impl Vec { /// [`as_mut_ptr`]: Vec::as_mut_ptr /// [`as_ptr`]: Vec::as_ptr /// [`as_non_null`]: Vec::as_non_null + /// [didn't allocate]: self#memory-layout #[stable(feature = "vec_as_ptr", since = "1.37.0")] #[rustc_const_stable(feature = "const_vec_string_slice", since = "1.87.0")] #[rustc_never_returns_null_ptr] @@ -1966,7 +1891,7 @@ impl Vec { } /// Returns a raw mutable pointer to the vector's buffer, or a dangling - /// raw pointer valid for zero sized reads if the vector didn't allocate. + /// raw pointer valid for zero sized reads if the vector [didn't allocate]. /// /// The caller must ensure that the vector outlives the pointer this /// function returns, or else it will end up dangling. @@ -2038,6 +1963,7 @@ impl Vec { /// [`as_non_null`]: Vec::as_non_null /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc /// [`ManuallyDrop`]: core::mem::ManuallyDrop + /// [didn't allocate]: self#memory-layout #[stable(feature = "vec_as_ptr", since = "1.37.0")] #[rustc_const_stable(feature = "const_vec_string_slice", since = "1.87.0")] #[rustc_never_returns_null_ptr] @@ -2050,7 +1976,7 @@ impl Vec { } /// Returns a `NonNull` pointer to the vector's buffer, or a dangling - /// `NonNull` pointer valid for zero sized reads if the vector didn't allocate. + /// `NonNull` pointer valid for zero sized reads if the vector [didn't allocate]. /// /// The caller must ensure that the vector outlives the pointer this /// function returns, or else it will end up dangling. @@ -2105,6 +2031,7 @@ impl Vec { /// [`as_mut_ptr`]: Vec::as_mut_ptr /// [`as_ptr`]: Vec::as_ptr /// [`as_non_null`]: Vec::as_non_null + /// [didn't allocate]: self#memory-layout #[unstable(feature = "vec_as_non_null", issue = "157843")] #[rustc_const_unstable(feature = "vec_as_non_null", issue = "157843")] #[rustc_as_ptr] From 93b75b29d4f538b44663f3256bb6ef6c9ef8ab95 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 10 Oct 2026 13:24:44 +0200 Subject: [PATCH 03/27] document some quirks around directory symlinks on Windows --- library/std/src/fs.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 139275ed2e1c2..1174f93b8e835 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -2257,10 +2257,9 @@ impl Metadata { FileType(self.0.file_type()) } - /// Returns `true` if this metadata is for a directory. The - /// result is mutually exclusive to the result of - /// [`Metadata::is_file`], and will be false for symlink metadata - /// obtained from [`symlink_metadata`]. + /// Returns `true` if this metadata is for a directory. The result is mutually exclusive to the + /// result of [`Metadata::is_file`] and [`Metadata::is_symlink`] (i.e., at most one of these + /// three methods returns `true`). /// /// # Examples /// @@ -2280,10 +2279,9 @@ impl Metadata { self.file_type().is_dir() } - /// Returns `true` if this metadata is for a regular file. The - /// result is mutually exclusive to the result of - /// [`Metadata::is_dir`], and will be false for symlink metadata - /// obtained from [`symlink_metadata`]. + /// Returns `true` if this metadata is for a regular file. The result is mutually exclusive to + /// the result of [`Metadata::is_dir`] and [`Metadata::is_symlink`] (i.e., at most one of these + /// three methods returns `true`). /// /// When the goal is simply to read from (or write to) the source, the most /// reliable way to test the source can be read (or written to) is to open @@ -2970,6 +2968,9 @@ impl AsInner for DirEntry { /// On Windows, `DeleteFile` is used or `CreateFileW` and `SetInformationByHandle` for readonly files. /// Note that, this [may change in the future][changes]. /// +/// On Windows, symbolic links to directories cannot currently be removed with this function. +/// Use [`remove_dir`] for that purpose. +/// /// [changes]: io#platform-specific-behavior /// /// # Errors @@ -3458,6 +3459,9 @@ pub fn create_dir_all>(path: P) -> io::Result<()> { /// and the `RemoveDirectory` function on Windows. /// Note that, this [may change in the future][changes]. /// +/// On Windows, this can also be used to remove symbolic links to directories. +/// On Unix, [`remove_file`] must be used for that purpose. +/// /// [changes]: io#platform-specific-behavior /// /// # Errors From 311c6e39ad6481a30ac3ef87735299fed86b6127 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 11 Oct 2026 20:42:55 +0200 Subject: [PATCH 04/27] Revert "Rollup merge of #159562 - Joel-Wwalker:154071-stdio-handle-cache, r=aapoalas" This reverts commit 556c0e625b9f24942a6ee2cc3ef1683c60868d6a, reversing changes made to 2cb8b181007fd50c394fbbe16b115e942c5b62ab. --- library/std/src/io/stdio.rs | 45 +--------------- library/std/src/sys/stdio/windows.rs | 77 ++++------------------------ 2 files changed, 13 insertions(+), 109 deletions(-) diff --git a/library/std/src/io/stdio.rs b/library/std/src/io/stdio.rs index 0848f97e69d32..527671442fb72 100644 --- a/library/std/src/io/stdio.rs +++ b/library/std/src/io/stdio.rs @@ -95,27 +95,6 @@ const fn stderr_raw() -> StderrRaw { StderrRaw(stdio::Stderr::new()) } -#[cfg(windows)] -impl StdoutRaw { - /// Starts a new lock session: stream state cached per lock session (the - /// handle and its console mode) is re-queried at the next write, so that - /// changing the process stdio handles (e.g. with `SetStdHandle`) between - /// lock sessions keeps working. - #[inline] - fn refresh(&mut self) { - self.0.refresh(); - } -} - -#[cfg(windows)] -impl StderrRaw { - /// See `StdoutRaw::refresh`. - #[inline] - fn refresh(&mut self) { - self.0.refresh(); - } -} - impl Read for StdinRaw { fn read(&mut self, buf: &mut [u8]) -> io::Result { handle_ebadf(self.0.read(buf), || Ok(0)) @@ -790,19 +769,7 @@ impl Stdout { // Locks this handle with 'static lifetime. This depends on the // implementation detail that the underlying `ReentrantMutex` is // static. - let lock = StdoutLock { inner: self.inner.lock() }; - // A new lock session begins, so let Windows re-query cached stream - // state at the next write; other platforms cache nothing, and skip - // this entirely so their `lock()` is unchanged. Skipped also if the - // cell is already borrowed (a nested lock during an ongoing write); - // such a lock is part of the outer session anyway. - #[cfg(windows)] - { - if let Ok(mut w) = lock.inner.try_borrow_mut() { - w.get_mut().refresh(); - } - } - lock + StdoutLock { inner: self.inner.lock() } } } @@ -1034,15 +1001,7 @@ impl Stderr { // Locks this handle with 'static lifetime. This depends on the // implementation detail that the underlying `ReentrantMutex` is // static. - let lock = StderrLock { inner: self.inner.lock() }; - // See `Stdout::lock`: a new lock session begins (Windows only). - #[cfg(windows)] - { - if let Ok(mut w) = lock.inner.try_borrow_mut() { - w.refresh(); - } - } - lock + StderrLock { inner: self.inner.lock() } } } diff --git a/library/std/src/sys/stdio/windows.rs b/library/std/src/sys/stdio/windows.rs index d9c9290ed21a2..1339048a9c039 100644 --- a/library/std/src/sys/stdio/windows.rs +++ b/library/std/src/sys/stdio/windows.rs @@ -22,35 +22,12 @@ pub struct Stdin { pub struct Stdout { incomplete_utf8: IncompleteUtf8, - write_mode: Option, } pub struct Stderr { incomplete_utf8: IncompleteUtf8, - write_mode: Option, } -/// How to write to a standard stream, cached for the duration of a lock -/// session (see `Stdout::refresh`). Re-querying this on every lock -/// acquisition keeps `SetStdHandle` calls made between lock sessions working -/// (see #40490), while a held lock skips the per-write console queries that -/// otherwise dominate bulk writes (see #154071). -#[derive(Clone, Copy)] -enum WriteMode { - /// The stream is a pipe or file, or a console using the UTF-8 code page: - /// bytes are written out unchanged. - Passthrough(c::HANDLE), - /// The stream is a console using a non-UTF-8 code page: data is converted - /// to UTF-16 and written with `WriteConsoleW`. - Utf16Console(c::HANDLE), -} - -// SAFETY: a `HANDLE` stored here is only an OS handle value for a standard -// stream (it is never dereferenced), and handle values are valid -// process-wide, on any thread. -unsafe impl Send for WriteMode {} -unsafe impl Sync for WriteMode {} - struct IncompleteUtf8 { bytes: [u8; 4], len: u8, @@ -122,40 +99,21 @@ fn is_utf8_console() -> bool { false } -fn write( - handle_id: u32, - data: &[u8], - incomplete_utf8: &mut IncompleteUtf8, - write_mode: &mut Option, -) -> io::Result { +fn write(handle_id: u32, data: &[u8], incomplete_utf8: &mut IncompleteUtf8) -> io::Result { if data.is_empty() { return Ok(0); } - let mode = match *write_mode { - Some(mode) => mode, - None => { - let handle = get_handle(handle_id)?; - let mode = if !is_console(handle) || is_utf8_console() { - WriteMode::Passthrough(handle) - } else { - WriteMode::Utf16Console(handle) - }; - // Only cache success; if there is no handle, keep erroring on - // every write like before. - *write_mode = Some(mode); - mode - } - }; - - match mode { - WriteMode::Passthrough(handle) => unsafe { + let handle = get_handle(handle_id)?; + if !is_console(handle) || is_utf8_console() { + unsafe { let handle = Handle::from_raw_handle(handle); let ret = handle.write(data); let _ = handle.into_raw_handle(); // Don't close the handle ret - }, - WriteMode::Utf16Console(handle) => write_console_utf16(data, incomplete_utf8, handle), + } + } else { + write_console_utf16(data, incomplete_utf8, handle) } } @@ -481,21 +439,13 @@ impl IncompleteUtf8 { impl Stdout { pub const fn new() -> Stdout { - Stdout { incomplete_utf8: IncompleteUtf8::new(), write_mode: None } - } - - /// Forgets the cached stream state, so the next write re-queries the OS. - /// Called when a new lock session begins, so that changing the process - /// stdio handles (e.g. `SetStdHandle`) between lock sessions keeps - /// working (see #40490). - pub fn refresh(&mut self) { - self.write_mode = None; + Stdout { incomplete_utf8: IncompleteUtf8::new() } } } impl io::Write for Stdout { fn write(&mut self, buf: &[u8]) -> io::Result { - write(c::STD_OUTPUT_HANDLE, buf, &mut self.incomplete_utf8, &mut self.write_mode) + write(c::STD_OUTPUT_HANDLE, buf, &mut self.incomplete_utf8) } fn flush(&mut self) -> io::Result<()> { @@ -505,18 +455,13 @@ impl io::Write for Stdout { impl Stderr { pub const fn new() -> Stderr { - Stderr { incomplete_utf8: IncompleteUtf8::new(), write_mode: None } - } - - /// See `Stdout::refresh`. - pub fn refresh(&mut self) { - self.write_mode = None; + Stderr { incomplete_utf8: IncompleteUtf8::new() } } } impl io::Write for Stderr { fn write(&mut self, buf: &[u8]) -> io::Result { - write(c::STD_ERROR_HANDLE, buf, &mut self.incomplete_utf8, &mut self.write_mode) + write(c::STD_ERROR_HANDLE, buf, &mut self.incomplete_utf8) } fn flush(&mut self) -> io::Result<()> { From 5955fed7d531a55351ecf4139a8863d84da34def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 28 Sep 2026 14:55:15 +0200 Subject: [PATCH 05/27] add breaking test for 161913 --- .../fudge-eager-resolve-breakage-3.rs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/ui/coercion/fudge-inference/fudge-eager-resolve-breakage-3.rs diff --git a/tests/ui/coercion/fudge-inference/fudge-eager-resolve-breakage-3.rs b/tests/ui/coercion/fudge-inference/fudge-eager-resolve-breakage-3.rs new file mode 100644 index 0000000000000..1368e8e019873 --- /dev/null +++ b/tests/ui/coercion/fudge-inference/fudge-eager-resolve-breakage-3.rs @@ -0,0 +1,42 @@ +//@ check-pass +// When we changed from lazy resolving to eager resolving, this was in some cases breaking +// We introduced a workaround in https://github.com/rust-lang/rust/pull/158447 to more often +// avoid breakage. This workaround was not perfect, and we knew this when landing, +// we just had no examples that showed the breakage until this one. +// +// The workaround would try to fudge a formally expected type, but if no structural changes +// to the type happened, would rollback the fudging again, and stuck to the formal expected type. +// +// This rollback behavior worked per function argument, and that's what goes wrong in this +// reproducer: g() is called with two parameters. The 2nd parameter gets fudged, and has a +// structural change (the return type is inferred to unit), so fudging gets accepted, +// not rolled back. In the process, the input of the closure, `x` becomes an unconstrained +// type variable, and type inference fails, despite us knowing that its type is `X` already, +// by the difinion of `g`. + +fn f() -> X { + let v = h(g(X, |x| x.m())); + // ^^^^^^^^^^^^^^ two parameters + v +} + +fn g(_: T, _: fn(T) -> U) -> Y { + loop {} +} + +fn h(_: Y) -> U { + loop {} +} + +struct X; + +impl X { + fn m(self) {} +} + +struct Y { + a: fn(T) -> T, + b: U, +} + +fn main() {} From 7a9e4097612f0d4dec011908d5410aabb850d38d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 28 Sep 2026 15:34:22 +0200 Subject: [PATCH 06/27] fix fudging and three other more known issues --- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 122 ++++++++++-------- 1 file changed, 65 insertions(+), 57 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 68eb349bab8f0..7c8de85dfee58 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -17,11 +17,11 @@ use rustc_hir_analysis::check::potentially_plural_count; use rustc_hir_analysis::hir_ty_lowering::{HirTyLowerer, ResolvedStructPath}; use rustc_index::IndexVec; use rustc_infer::infer::{ - BoundRegionConversionTime, DefineOpaqueTypes, InferOk, TypeTrace, relate, + BoundRegionConversionTime, DefineOpaqueTypes, InferCtxt, InferOk, TypeTrace, relate, }; use rustc_infer::traits::WellFormedLoc; use rustc_middle::ty::adjustment::AllowTwoPhase; -use rustc_middle::ty::error::{ExpectedFound, TypeError}; +use rustc_middle::ty::error::TypeError; use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_middle::ty::relate::{Relate, RelateResult, TypeRelation}; use rustc_middle::ty::{self, IsSuggestable, Ty, TyCtxt, TypeVisitableExt, Unnormalized}; @@ -293,22 +293,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected_input_tys .into_iter() .zip(formal_input_tys) - // if the expected input type is structurally equal to the formal input type, - // i.e. we've only changed some inference variables around, keep the formal - // input ty as the expected input ty. Usually fudging helps because it gains - // information from a callsite of a function. However, Fudging also sometimes - // loses information, when the original, formal, input type had constraints on it, - // and fudging replaces all inference variables with fresh ones, those constraints - // are discarded. This check makes sure we only keep fudging output if structural - // changes were made to the type. If all that was changed were some typevars, - // we go back to the unfudged formal input type. .map(|(expected_input_ty, formal_input_ty)| { - if same_type_modulo_vars(tcx, expected_input_ty, *formal_input_ty) { - // if they're the same, fall back to the formal input type - *formal_input_ty - } else { - expected_input_ty - } + only_keep_structural_changes( + &self.infcx, + *formal_input_ty, + expected_input_ty, + ) }) .collect() })) @@ -3612,50 +3602,63 @@ enum SuggestionText { DidYouMean, } -fn same_type_modulo_vars<'tcx>(tcx: TyCtxt<'tcx>, a: Ty<'tcx>, b: Ty<'tcx>) -> bool { - struct SameModuloVars<'tcx> { - tcx: TyCtxt<'tcx>, +/// Postprocessing step of fudging: after fudging, any changes from the formal input type to the +/// expected input type are only kept, if they result in structural changes. If the only change is +/// one inference variable changing to another (fresh) inference variable, the formal one is kept, +/// not the fresh one. This avoids cases in which those fresh inference variables are *less* +/// constrained than the original formal ones, and making them fresh has no benefit. +fn only_keep_structural_changes<'tcx>( + infcx: &InferCtxt<'tcx>, + formal: Ty<'tcx>, + expected: Ty<'tcx>, +) -> Ty<'tcx> { + struct OnlyKeepStructuralChanges<'a, 'tcx> { + infcx: &'a InferCtxt<'tcx>, } - impl<'tcx> TypeRelation> for SameModuloVars<'tcx> { + impl<'tcx> TypeRelation> for OnlyKeepStructuralChanges<'_, 'tcx> { fn cx(&self) -> TyCtxt<'tcx> { - self.tcx + self.infcx.tcx } fn relate_ty_args( &mut self, - a_ty: Ty<'tcx>, - _b_ty: Ty<'tcx>, - _ty_def_id: DefId, - a_args: ty::GenericArgsRef<'tcx>, - b_args: ty::GenericArgsRef<'tcx>, - _mk: impl FnOnce(ty::GenericArgsRef<'tcx>) -> Ty<'tcx>, + formal_ty: Ty<'tcx>, + _expected_ty: Ty<'tcx>, + _def_id: DefId, + formal_args: ty::GenericArgsRef<'tcx>, + expected_args: ty::GenericArgsRef<'tcx>, + mk: impl FnOnce(ty::GenericArgsRef<'tcx>) -> Ty<'tcx>, ) -> RelateResult<'tcx, Ty<'tcx>> { - relate::relate_args_invariantly(self, a_args, b_args)?; - Ok(a_ty) + let new_args = relate::relate_args_invariantly(self, formal_args, expected_args)?; + + if new_args == formal_args { Ok(formal_ty) } else { Ok(mk(new_args)) } } fn relate_with_variance>>( &mut self, _variance: ty::Variance, _info: ty::VarianceDiagInfo>, - a: T, - b: T, + formal: T, + expected: T, ) -> RelateResult<'tcx, T> { - self.relate(a, b) + self.relate(formal, expected) } - fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { - if a == b { - return Ok(a); + fn tys(&mut self, formal: Ty<'tcx>, expected: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { + if formal == expected { + return Ok(formal); } - match (a.kind(), b.kind()) { + match (formal.kind(), expected.kind()) { + // If both are an infer var, keep the formal one. (&ty::Infer(ty::InferTy::TyVar(_)), &ty::Infer(ty::InferTy::TyVar(_))) | (&ty::Infer(ty::InferTy::FloatVar(_)), &ty::Infer(ty::InferTy::FloatVar(_))) - | (&ty::Infer(ty::InferTy::IntVar(_)), &ty::Infer(ty::InferTy::IntVar(_))) => Ok(a), - (&ty::Infer(_), _) | (_, &ty::Infer(_)) => Err(TypeError::Mismatch), + | (&ty::Infer(ty::InferTy::IntVar(_)), &ty::Infer(ty::InferTy::IntVar(_))) => { + Ok(formal) + } + (&ty::Infer(_), _) | (_, &ty::Infer(_)) => Ok(expected), (&ty::Error(guar), _) | (_, &ty::Error(guar)) => Ok(Ty::new_error(self.cx(), guar)), - _ => relate::structurally_relate_tys(self, a, b), + _ => relate::structurally_relate_tys(self, formal, expected), } } @@ -3669,42 +3672,47 @@ fn same_type_modulo_vars<'tcx>(tcx: TyCtxt<'tcx>, a: Ty<'tcx>, b: Ty<'tcx>) -> b fn consts( &mut self, - mut a: ty::Const<'tcx>, - mut b: ty::Const<'tcx>, + mut formal: ty::Const<'tcx>, + mut expected: ty::Const<'tcx>, ) -> RelateResult<'tcx, ty::Const<'tcx>> { - if a == b { - return Ok(a); + if formal == expected { + return Ok(formal); } // Avoid ICEs when in gce, and `structurally_relate_consts` // turns a non-infer const into an infer const - if self.tcx.features().generic_const_exprs() { - a = self.tcx.expand_abstract_consts(a); - b = self.tcx.expand_abstract_consts(b); + if self.cx().features().generic_const_exprs() { + formal = self.cx().expand_abstract_consts(formal); + expected = self.cx().expand_abstract_consts(expected); } - match (a.kind(), b.kind()) { - (ty::ConstKind::Infer(_), ty::ConstKind::Infer(_)) => return Ok(a), - (ty::ConstKind::Infer(_), _) | (_, ty::ConstKind::Infer(_)) => { - return Err(TypeError::ConstMismatch(ExpectedFound::new(a, b))); - } + match (formal.kind(), expected.kind()) { + // If both are an infer var, keep the formal one. + (ty::ConstKind::Infer(_), ty::ConstKind::Infer(_)) => return Ok(formal), + (ty::ConstKind::Infer(_), _) | (_, ty::ConstKind::Infer(_)) => return Ok(formal), + _ => {} } - relate::structurally_relate_consts(self, a, b) + relate::structurally_relate_consts(self, formal, expected) } fn binders( &mut self, - a: ty::Binder<'tcx, T>, - b: ty::Binder<'tcx, T>, + formal: ty::Binder<'tcx, T>, + expected: ty::Binder<'tcx, T>, ) -> RelateResult<'tcx, ty::Binder<'tcx, T>> where T: Relate>, { - Ok(a.rebind(self.relate(a.skip_binder(), b.skip_binder())?)) + Ok(formal.rebind(self.relate(formal.skip_binder(), expected.skip_binder())?)) } } - SameModuloVars { tcx }.relate(a, b).is_ok() + // If it errors for some reason, keep the expected type, not the formal one, keeping the fudging. + let res = OnlyKeepStructuralChanges { infcx }.relate(formal, expected).unwrap_or(expected); + tracing::debug!( + "formal: {formal:?} and expected {expected:?} => only keeping structural changes creates {res:?}" + ); + res } From fdefc162fb248b2f516e2827015f209487820d0e Mon Sep 17 00:00:00 2001 From: Francisco Gouveia Date: Sun, 11 Oct 2026 18:32:31 +0100 Subject: [PATCH 07/27] Add a new `rustc_show_spans` internal attribute This new attribute is a first step for solving issue 126763. Now, we can easily test out where spans end up, particularly useful for ensuring that spans end up in the best suitable macro context. --- compiler/rustc_ast_passes/src/diagnostics.rs | 8 +++ compiler/rustc_ast_passes/src/lib.rs | 1 + compiler/rustc_ast_passes/src/show_spans.rs | 67 +++++++++++++++++++ compiler/rustc_attr_ir/src/data_structures.rs | 10 +++ .../rustc_attr_ir/src/encode_cross_crate.rs | 2 + .../src/attributes/rustc_dump.rs | 43 +++++++++++- compiler/rustc_attr_parsing/src/context.rs | 1 + compiler/rustc_feature/src/builtin_attrs.rs | 1 + compiler/rustc_interface/src/passes.rs | 4 ++ compiler/rustc_passes/src/check_attr.rs | 1 + compiler/rustc_span/src/symbol.rs | 1 + 11 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 compiler/rustc_ast_passes/src/show_spans.rs diff --git a/compiler/rustc_ast_passes/src/diagnostics.rs b/compiler/rustc_ast_passes/src/diagnostics.rs index bb006b32ac73e..7ade88ca317c2 100644 --- a/compiler/rustc_ast_passes/src/diagnostics.rs +++ b/compiler/rustc_ast_passes/src/diagnostics.rs @@ -1325,3 +1325,11 @@ pub(crate) struct AbiCustomCannotBeCold { #[label("`extern \"custom\"` because of this")] pub abi_span: Span, } + +#[derive(Diagnostic)] +#[diag("showing the spans of the {$kind}")] +pub(crate) struct ShowSpan { + #[primary_span] + pub span: Span, + pub kind: &'static str, +} diff --git a/compiler/rustc_ast_passes/src/lib.rs b/compiler/rustc_ast_passes/src/lib.rs index 918a623b41752..5742d5a9e1a73 100644 --- a/compiler/rustc_ast_passes/src/lib.rs +++ b/compiler/rustc_ast_passes/src/lib.rs @@ -12,3 +12,4 @@ pub mod ast_validation; mod diagnostics; pub mod feature_gate; +pub mod show_spans; diff --git a/compiler/rustc_ast_passes/src/show_spans.rs b/compiler/rustc_ast_passes/src/show_spans.rs new file mode 100644 index 0000000000000..b28535653b0ed --- /dev/null +++ b/compiler/rustc_ast_passes/src/show_spans.rs @@ -0,0 +1,67 @@ +use rustc_ast::visit::{self, Visitor}; +use rustc_ast::{self as ast, ItemKind}; +use rustc_attr_ir::{Attribute, AttributeKind, RustcShowSpansKind}; +use rustc_attr_parsing::AttributeParser; +use rustc_session::Session; +use rustc_span::sym; + +use crate::diagnostics; + +struct ShowSpans<'a> { + sess: &'a Session, + expr: bool, + ty: bool, + path: bool, +} + +impl<'a> Visitor<'a> for ShowSpans<'a> { + fn visit_item(&mut self, item: &'a ast::Item) { + let saved = (self.expr, self.ty, self.path); + (self.expr, self.ty, self.path) = (false, false, false); + + if let ItemKind::Fn(_) = &item.kind + && let Some(Attribute::Parsed(AttributeKind::RustcShowSpans(kinds))) = + AttributeParser::parse_limited_sym(self.sess, &item.attrs, &[sym::rustc_show_spans]) + { + for kind in kinds { + match kind { + RustcShowSpansKind::Expr => self.expr = true, + RustcShowSpansKind::Ty => self.ty = true, + RustcShowSpansKind::Path => self.path = true, + } + } + } + visit::walk_item(self, item); + + // Recover the state. Needed if we are looking into nested items. + (self.expr, self.ty, self.path) = saved; + } + + // We override it as to not report attribute paths as paths. + fn visit_attribute(&mut self, _: &'a ast::Attribute) {} + + fn visit_expr(&mut self, e: &'a ast::Expr) { + if self.expr { + self.sess.dcx().emit_note(diagnostics::ShowSpan { span: e.span, kind: "expr" }); + } + visit::walk_expr(self, e); + } + + fn visit_ty(&mut self, t: &'a ast::Ty) { + if self.ty { + self.sess.dcx().emit_note(diagnostics::ShowSpan { span: t.span, kind: "ty" }); + } + visit::walk_ty(self, t); + } + + fn visit_path(&mut self, p: &'a ast::Path) { + if self.path { + self.sess.dcx().emit_note(diagnostics::ShowSpan { span: p.span, kind: "path" }); + } + visit::walk_path(self, p); + } +} + +pub fn check_crate(sess: &Session, krate: &ast::Crate) { + visit::walk_crate(&mut ShowSpans { sess, expr: false, ty: false, path: false }, krate); +} diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index 04fee06e2434c..1a6f32aa112cd 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -576,6 +576,13 @@ impl rustc_serialize::Encodable for DocAttribute } } +#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] +pub enum RustcShowSpansKind { + Expr, + Ty, + Path, +} + #[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] pub enum RustcDumpLayoutKind { Align, @@ -1407,6 +1414,9 @@ pub enum AttributeKind { /// Represents `#[rustc_should_not_be_called_on_const_items]` RustcShouldNotBeCalledOnConstItems, + /// Represents `#[rustc_show_spans]` + RustcShowSpans(ThinVec), + /// Represents `#[rustc_simd_monomorphize_lane_limit = "N"]`. RustcSimdMonomorphizeLaneLimit(Limit), diff --git a/compiler/rustc_attr_ir/src/encode_cross_crate.rs b/compiler/rustc_attr_ir/src/encode_cross_crate.rs index b6733e8c1db54..1bebec4ed0183 100644 --- a/compiler/rustc_attr_ir/src/encode_cross_crate.rs +++ b/compiler/rustc_attr_ir/src/encode_cross_crate.rs @@ -187,6 +187,7 @@ impl AttributeKind { RustcRegions => No, RustcScalableVector { .. } => Yes, RustcShouldNotBeCalledOnConstItems => Yes, + RustcShowSpans(_) => No, RustcSimdMonomorphizeLaneLimit(..) => Yes, // Affects layout computation, which needs to work cross-crate RustcSkipDuringMethodDispatch { .. } => No, RustcSpecializationTrait => No, @@ -401,6 +402,7 @@ impl AttributeKind { RustcRegions => false, RustcScalableVector { .. } => false, RustcShouldNotBeCalledOnConstItems => false, + RustcShowSpans(_) => false, RustcSimdMonomorphizeLaneLimit(..) => false, RustcSkipDuringMethodDispatch { .. } => false, RustcSpecializationTrait => false, diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs index 99c7e933b07b4..07ce47b8858e2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs @@ -1,5 +1,5 @@ use rustc_attr_ir::target::{AssocCtxt, MethodKind, Target}; -use rustc_attr_ir::{AttributeKind, RustcDumpLayoutKind}; +use rustc_attr_ir::{AttributeKind, RustcDumpLayoutKind, RustcShowSpansKind}; use rustc_feature::AttributeStability; use rustc_span::{Span, Symbol, sym}; @@ -301,3 +301,44 @@ impl NoArgsAttributeParser for RustcDumpVtableParser { const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcDumpVtable; } + +pub(crate) struct RustcShowSpans; + +impl SingleAttributeParser for RustcShowSpans { + const PATH: &[Symbol] = &[sym::rustc_show_spans]; + + const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Fn)]); + + const TEMPLATE: AttributeTemplate = template!(List: &["expr", "ty", "path"]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); + + fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { + let Some(items) = cx.expect_list(args, cx.attr_span) else { + return None; + }; + + let mut result = ThinVec::new(); + for item in items.mixed() { + let Some(arg) = item.meta_item_no_args() else { + cx.adcx().expected_not_literal(item.span()); + continue; + }; + let Some(ident) = arg.ident() else { + cx.adcx().expected_identifier(arg.span()); + return None; + }; + let kind = match ident.name { + sym::expr => RustcShowSpansKind::Expr, + sym::ty => RustcShowSpansKind::Ty, + sym::path => RustcShowSpansKind::Path, + _ => { + cx.adcx() + .expected_specific_argument(ident.span, &[sym::expr, sym::ty, sym::path]); + continue; + } + }; + result.push(kind); + } + Some(AttributeKind::RustcShowSpans(result)) + } +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 060fd74c1db21..b6b1ce18faca6 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -249,6 +249,7 @@ attribute_parsers!( Single, Single, Single, + Single, Single, Single, Single, diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index bc8c8f4b2a764..7c3c5c58a596b 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -384,6 +384,7 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ // Internal attributes, Testing: // ========================================================================== + sym::rustc_show_spans, sym::rustc_effective_visibility, sym::rustc_dump_inferred_outlives, sym::rustc_capture_analysis, diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 679de1e75613f..f0990d075e396 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -266,6 +266,10 @@ fn configure_and_expand( ) }); + if features.rustc_attrs() { + rustc_ast_passes::show_spans::check_crate(sess, &krate); + } + let crate_types = tcx.crate_types(); let is_executable_crate = crate_types.contains(&CrateType::Executable); let is_proc_macro_crate = crate_types.contains(&CrateType::ProcMacro); diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 3eb6892177b05..8bd68c26b9254 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -383,6 +383,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::RustcRegions => (), AttributeKind::RustcScalableVector { .. } => (), AttributeKind::RustcShouldNotBeCalledOnConstItems => (), + AttributeKind::RustcShowSpans(_) => (), AttributeKind::RustcSimdMonomorphizeLaneLimit(..) => (), AttributeKind::RustcSkipDuringMethodDispatch { .. } => (), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index a9275f489308a..3329b7b647279 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1913,6 +1913,7 @@ symbols! { rustc_regions, rustc_scalable_vector, rustc_should_not_be_called_on_const_items, + rustc_show_spans, rustc_simd_monomorphize_lane_limit, rustc_skip_during_method_dispatch, rustc_specialization_trait, From 9a20a071c20fe5bd68004b5d1aee909c21c46e33 Mon Sep 17 00:00:00 2001 From: Raushan singh Date: Sat, 12 Sep 2026 13:53:31 +0000 Subject: [PATCH 08/27] test(parser): add baseline for missing turbofish with lifetime in struct expr --- .../recover/missing-turbofish-lifetime.rs | 30 ++++++++++ .../recover/missing-turbofish-lifetime.stderr | 57 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 tests/ui/parser/recover/missing-turbofish-lifetime.rs create mode 100644 tests/ui/parser/recover/missing-turbofish-lifetime.stderr diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime.rs b/tests/ui/parser/recover/missing-turbofish-lifetime.rs new file mode 100644 index 0000000000000..596b197a317da --- /dev/null +++ b/tests/ui/parser/recover/missing-turbofish-lifetime.rs @@ -0,0 +1,30 @@ +struct Struct<'a> { + string: &'a str, +} + +fn struct_with_reserved_lifetime() { + let _ = Struct<'_> { + //~^ ERROR labels cannot use keyword names + //~| ERROR expected `while`, `for`, `loop` or `{` after a label + //~| ERROR comparison operators cannot be chained + string: "", + }; +} + +fn struct_with_named_lifetime() { + let _ = Struct<'a> { + //~^ ERROR expected `while`, `for`, `loop` or `{` after a label + //~| ERROR comparison operators cannot be chained + string: "", + }; +} + +fn struct_with_multichar_lifetime() { + let _ = Struct<'abc> { + //~^ ERROR expected `while`, `for`, `loop` or `{` after a label + //~| ERROR comparison operators cannot be chained + string: "", + }; +} + +fn main() {} diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime.stderr b/tests/ui/parser/recover/missing-turbofish-lifetime.stderr new file mode 100644 index 0000000000000..e14d9d6aa464c --- /dev/null +++ b/tests/ui/parser/recover/missing-turbofish-lifetime.stderr @@ -0,0 +1,57 @@ +error: labels cannot use keyword names + --> $DIR/missing-turbofish-lifetime.rs:6:20 + | +LL | let _ = Struct<'_> { + | ^^ + +error: expected `while`, `for`, `loop` or `{` after a label + --> $DIR/missing-turbofish-lifetime.rs:6:22 + | +LL | let _ = Struct<'_> { + | ^ expected `while`, `for`, `loop` or `{` after a label + | +help: add `'` to close the char literal + | +LL | let _ = Struct<'_'> { + | + + +error: comparison operators cannot be chained + --> $DIR/missing-turbofish-lifetime.rs:6:19 + | +LL | let _ = Struct<'_> { + | ^ ^ + +error: expected `while`, `for`, `loop` or `{` after a label + --> $DIR/missing-turbofish-lifetime.rs:15:22 + | +LL | let _ = Struct<'a> { + | ^ expected `while`, `for`, `loop` or `{` after a label + | +help: add `'` to close the char literal + | +LL | let _ = Struct<'a'> { + | + + +error: comparison operators cannot be chained + --> $DIR/missing-turbofish-lifetime.rs:15:19 + | +LL | let _ = Struct<'a> { + | ^ ^ + +error: expected `while`, `for`, `loop` or `{` after a label + --> $DIR/missing-turbofish-lifetime.rs:23:24 + | +LL | let _ = Struct<'abc> { + | ^ expected `while`, `for`, `loop` or `{` after a label + +error: comparison operators cannot be chained + --> $DIR/missing-turbofish-lifetime.rs:23:19 + | +LL | let _ = Struct<'abc> { + | ^ ^ + | + = help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + = help: or use `(...)` if you meant to specify fn arguments + +error: aborting due to 7 previous errors + From 508d2f42744343a6096b0e642565c9c919dadb58 Mon Sep 17 00:00:00 2001 From: Robin <1187338299@qq.com> Date: Tue, 13 Oct 2026 18:00:21 +0800 Subject: [PATCH 09/27] test DSL eagerly handle false constraints --- compiler/rustc_ast/src/ast.rs | 1 + compiler/rustc_ast_lowering/src/item.rs | 3 ++- compiler/rustc_hir/src/hir.rs | 1 + compiler/rustc_hir/src/intravisit.rs | 2 +- .../rustc_hir_analysis/src/check/wfcheck.rs | 17 ++++++++++++++++- compiler/rustc_hir_analysis/src/collect.rs | 4 ++-- compiler/rustc_parse/src/parser/item.rs | 4 +++- .../test-forall-evaluates-to-false.rs | 15 +++++++++++++++ .../test-forall-evaluates-to-false.stderr | 8 ++++++++ .../test-top-level-false-constraint.rs | 11 +++++++++++ .../test-top-level-false-constraint.stderr | 8 ++++++++ 11 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.rs create mode 100644 tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.stderr create mode 100644 tests/ui/assumptions_on_binders/test-top-level-false-constraint.rs create mode 100644 tests/ui/assumptions_on_binders/test-top-level-false-constraint.stderr diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 4c7ae87556f76..4effdd42f347a 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -4073,6 +4073,7 @@ pub struct TestBinderConstraints { #[derive(Clone, Encodable, Decodable, Debug, Walkable)] pub struct TestBinderBody { + pub span: Span, pub foralls: ThinVec, pub exists: ThinVec, pub constraints: Vec, diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index babca94628891..8203e6f98bd4f 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -2005,7 +2005,8 @@ impl<'hir> LoweringContext<'_, 'hir> { .iter() .map(|predicate| self.lower_where_predicate(predicate, &[], &mut dedup_map)), ); - hir::TestBinderBody { foralls, exists, constraints, predicates } + let span = self.lower_span(body.span); + hir::TestBinderBody { foralls, exists, constraints, predicates, span } } fn lower_test_binder_forall( diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index dfcb8871fe982..70702c5d42b59 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -4527,6 +4527,7 @@ impl FnHeader { #[derive(Debug, Clone, Copy, StableHash)] pub struct TestBinderBody<'hir> { + pub span: Span, pub foralls: &'hir [TestBinderForall<'hir>], pub exists: &'hir [TestBinderExists<'hir>], /// Constraints to be inserted directly into constraint storage to be proven diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index ab81862b74e32..ba82a1adc7d48 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -1600,7 +1600,7 @@ pub fn walk_test_binder_body<'v, V: Visitor<'v>>( visitor: &mut V, body: &'v TestBinderBody<'v>, ) -> V::Result { - let TestBinderBody { foralls, exists, constraints, predicates } = body; + let TestBinderBody { span: _, foralls, exists, constraints, predicates } = body; walk_list!(visitor, visit_test_binder_forall, *foralls); walk_list!(visitor, visit_test_binder_exists, *exists); try_visit!(visitor.visit_test_binder_constraint(&constraints)); diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 0599f036848c5..1aa194d7e0013 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2379,7 +2379,7 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { #[instrument(level = "debug", skip(self))] pub(super) fn check_test_binder_body(&self, body: TestBinderBody<'tcx>) { - let TestBinderBody { foralls, exists, constraints, predicates } = body; + let TestBinderBody { span, foralls, exists, constraints, predicates } = body; if !predicates.is_empty() { for (predicate, span) in predicates { let cause = traits::ObligationCause::misc(span, self.body_def_id); @@ -2399,6 +2399,13 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { Ok(()) => constraints, Err(_guar) => ty::region_constraint::RegionConstraint::new_true(), }; + if constraints.is_false() { + self.tcx() + .dcx() + .struct_span_err(span, "top level false constraints are not supported") + .emit(); + return; + } self.infcx.register_solver_region_constraint(constraints); @@ -2478,6 +2485,13 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { u, ) .with_spans(forall.span); + if constraint.is_false() { + self.tcx().dcx().struct_span_err(forall.span, "forall evaluated to false").emit(); + self.infcx.overwrite_solver_region_constraint( + ty::region_constraint::RegionConstraint::new_true(), + ); + return; + } if let Some(assert_on_exit) = &forall.assert_on_exit { self.check_test_binder_region_constraints(forall.span, assert_on_exit, &constraint); } @@ -2797,6 +2811,7 @@ struct RedundantLifetimeArgsLint<'tcx> { #[derive(Clone, Debug, TypeFoldable, TypeVisitable)] pub(crate) struct TestBinderBody<'tcx> { + pub span: Span, pub foralls: Vec>, pub exists: Vec>, /// Constraints to be inserted directly into constraint storage to be proven diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 1965ade38204e..7f181ef2e7e04 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -328,7 +328,7 @@ impl<'tcx> ItemCtxt<'tcx> { &self, item: &hir::TestBinderBody<'tcx>, ) -> TestBinderBody<'tcx> { - let hir::TestBinderBody { foralls, exists, constraints, predicates } = item; + let hir::TestBinderBody { span, foralls, exists, constraints, predicates } = item; let foralls = foralls.iter().map(|forall| self.lower_test_binder_forall(forall)).collect(); let exists = exists.iter().map(|exists| self.lower_test_binder_exists(exists)).collect(); let constraints = self.lower_test_binder_constraint(&constraints); @@ -337,7 +337,7 @@ impl<'tcx> ItemCtxt<'tcx> { clauses_of::where_predicate_clauses(self, predicate, &mut clauses); } let predicates = clauses.into_iter().map(|(c, span)| (c.kind(), span)).collect(); - TestBinderBody { foralls, exists, constraints, predicates } + TestBinderBody { span: *span, foralls, exists, constraints, predicates } } #[instrument(level = "debug", skip(self), ret)] diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 6d3d79bbe2b5c..e7e0434c2fa2e 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -2717,6 +2717,7 @@ impl<'a> Parser<'a> { let mut exists = ThinVec::new(); let mut constraints = Vec::new(); let mut predicates = Vec::new(); + let mut span = self.token.span; self.parse_delim_comma_seq(exp!(OpenBrace), exp!(CloseBrace), |this| { match this.token.ident() { Some((Ident { name: sym::predicates, .. }, IdentKind::Normal)) => { @@ -2735,7 +2736,8 @@ impl<'a> Parser<'a> { } Ok(()) })?; - Ok(TestBinderBody { foralls, exists, constraints, predicates }) + span = span.to(self.prev_token.span); + Ok(TestBinderBody { foralls, exists, constraints, predicates, span }) } pub fn parse_test_binder_forall(&mut self) -> PResult<'a, TestBinderForall> { diff --git a/tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.rs b/tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.rs new file mode 100644 index 0000000000000..c98afb8474448 --- /dev/null +++ b/tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.rs @@ -0,0 +1,15 @@ +//@ compile-flags: -Zassumptions-on-binders -Znext-solver=globally + +#![feature(test_binder_constraints, non_lifetime_binders)] +#![expect(incomplete_features)] + +core::test_binder_constraints! { + impl<'a> { + forall<'b> { + //~^ ERROR forall evaluated to false + 'b: 'a + } + } +} + +fn main() {} diff --git a/tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.stderr b/tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.stderr new file mode 100644 index 0000000000000..995363d521621 --- /dev/null +++ b/tests/ui/assumptions_on_binders/test-forall-evaluates-to-false.stderr @@ -0,0 +1,8 @@ +error: forall evaluated to false + --> $DIR/test-forall-evaluates-to-false.rs:8:9 + | +LL | forall<'b> { + | ^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/assumptions_on_binders/test-top-level-false-constraint.rs b/tests/ui/assumptions_on_binders/test-top-level-false-constraint.rs new file mode 100644 index 0000000000000..cdd7eb7a321bc --- /dev/null +++ b/tests/ui/assumptions_on_binders/test-top-level-false-constraint.rs @@ -0,0 +1,11 @@ +//@ compile-flags: -Zassumptions-on-binders -Znext-solver=globally + +#![feature(test_binder_constraints, non_lifetime_binders)] +#![expect(incomplete_features)] + +core::test_binder_constraints! { + impl { or {} } + //~^ ERROR top level false constraints are not supported +} + +fn main() {} diff --git a/tests/ui/assumptions_on_binders/test-top-level-false-constraint.stderr b/tests/ui/assumptions_on_binders/test-top-level-false-constraint.stderr new file mode 100644 index 0000000000000..fb5380879fe3d --- /dev/null +++ b/tests/ui/assumptions_on_binders/test-top-level-false-constraint.stderr @@ -0,0 +1,8 @@ +error: top level false constraints are not supported + --> $DIR/test-top-level-false-constraint.rs:7:10 + | +LL | impl { or {} } + | ^^^^^^^^^ + +error: aborting due to 1 previous error + From ebca0756e0812a8c971788d2df71bcec70f9aa66 Mon Sep 17 00:00:00 2001 From: Raushan singh Date: Sat, 3 Oct 2026 03:43:17 +0000 Subject: [PATCH 10/27] fix(parser): recover missing turbofish for lifetimes in exprs --- compiler/rustc_parse/src/diagnostics.rs | 9 ++ .../rustc_parse/src/parser/diagnostics.rs | 7 + compiler/rustc_parse/src/parser/path.rs | 17 +++ tests/ui/parser/issues/issue-93282.rs | 8 +- tests/ui/parser/issues/issue-93282.stderr | 47 ++++--- .../missing-turbofish-lifetime-macro.rs | 16 +++ .../missing-turbofish-lifetime-macro.stderr | 20 +++ .../recover/missing-turbofish-lifetime.fixed | 87 ++++++++++++ .../recover/missing-turbofish-lifetime.rs | 75 +++++++++-- .../recover/missing-turbofish-lifetime.stderr | 127 +++++++++++++----- .../require-parens-for-chained-comparison.rs | 16 +-- ...quire-parens-for-chained-comparison.stderr | 67 ++++----- 12 files changed, 375 insertions(+), 121 deletions(-) create mode 100644 tests/ui/parser/recover/missing-turbofish-lifetime-macro.rs create mode 100644 tests/ui/parser/recover/missing-turbofish-lifetime-macro.stderr create mode 100644 tests/ui/parser/recover/missing-turbofish-lifetime.fixed diff --git a/compiler/rustc_parse/src/diagnostics.rs b/compiler/rustc_parse/src/diagnostics.rs index 54d91eca01327..a942d6b060dab 100644 --- a/compiler/rustc_parse/src/diagnostics.rs +++ b/compiler/rustc_parse/src/diagnostics.rs @@ -1712,6 +1712,15 @@ pub(crate) struct GenericParamsWithoutAngleBrackets { pub sugg: GenericParamsWithoutAngleBracketsSugg, } +#[derive(Diagnostic)] +#[diag("use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments")] +pub(crate) struct MissingTurbofishForLifetime { + #[primary_span] + pub span: Span, + #[suggestion("add `::`", style = "verbose", code = "::", applicability = "machine-applicable")] + pub suggest_turbofish: Span, +} + #[derive(Subdiagnostic)] #[multipart_suggestion( "surround the type parameters with angle brackets", diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 14ea328601a02..18221f2fb8d7e 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -1143,6 +1143,13 @@ impl<'a> Parser<'a> { } } + pub(super) fn recover_missing_turbofish_for_lifetimes(&self, lt_span: Span) { + self.dcx().emit_err(crate::diagnostics::MissingTurbofishForLifetime { + span: lt_span, + suggest_turbofish: lt_span.shrink_to_lo(), + }); + } + /// When writing a turbofish with multiple type parameters missing the leading `::`, we will /// encounter a parse error when encountering the first `,`. pub(super) fn check_mistyped_turbofish_with_multiple_type_params( diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index 99bf592c859fb..b9ba5532f5708 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -303,9 +303,20 @@ impl<'a> Parser<'a> { is_args_start(&this.token) }; + // A lifetime followed by `>` or `,` can never start a valid expression, so `<` here + // must be the start of generic arguments with a missing `::`. + let is_missing_turbofish_for_lifetime = |this: &mut Self| { + this.token == token::Lt + && this.may_recover() + && style == PathStyle::Expr + && this.look_ahead(1, |t| t.is_lifetime()) + && this.look_ahead(2, |t| matches!(t.kind, token::Gt | token::Comma)) + }; + Ok( if style == PathStyle::Type && check_args_start(self) || style != PathStyle::Mod && self.check_path_sep_and_look_ahead(is_args_start) + || is_missing_turbofish_for_lifetime(self) { // We use `style == PathStyle::Expr` to check if this is in a recursion or not. If // it isn't, then we reset the unmatched angle bracket count as we're about to start @@ -314,12 +325,18 @@ impl<'a> Parser<'a> { self.unmatched_angle_bracket_count = 0; } + let missing_turbofish = + style == PathStyle::Expr && !self.check_path_sep_and_look_ahead(is_args_start); + // Generic arguments are found - `<`, `(`, `::<` or `::(`. // First, eat `::` if it exists. let _ = self.eat_path_sep(); let lo = self.token.span; let args = if self.eat_lt() { + if missing_turbofish { + self.recover_missing_turbofish_for_lifetimes(lo); + } // `<'a, T, A = U>` let args = self.parse_angle_args_with_leading_angle_bracket_recovery( style, diff --git a/tests/ui/parser/issues/issue-93282.rs b/tests/ui/parser/issues/issue-93282.rs index 274245f1a465f..5b96969f5b971 100644 --- a/tests/ui/parser/issues/issue-93282.rs +++ b/tests/ui/parser/issues/issue-93282.rs @@ -1,7 +1,10 @@ fn main() { + //~^ HELP consider introducing lifetime `'a` here f<'a,> - //~^ ERROR expected - //~| ERROR expected + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + //~| ERROR use of undeclared lifetime name `'a` + //~| ERROR cannot find value `f` in this scope } fn bar(a: usize, b: usize) -> usize { @@ -12,5 +15,6 @@ fn foo() { let x = 1; bar('y, x); //~^ ERROR expected + //~| HELP add `'` to close the char literal //~| ERROR mismatched types } diff --git a/tests/ui/parser/issues/issue-93282.stderr b/tests/ui/parser/issues/issue-93282.stderr index c6140bb821e48..1daacff52f066 100644 --- a/tests/ui/parser/issues/issue-93282.stderr +++ b/tests/ui/parser/issues/issue-93282.stderr @@ -1,27 +1,16 @@ -error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/issue-93282.rs:2:9 - | -LL | f<'a,> - | ^ expected `while`, `for`, `loop` or `{` after a label - | -help: add `'` to close the char literal - | -LL | f<'a',> - | + - -error: expected one of `.`, `:`, `;`, `?`, `for`, `loop`, `while`, `}`, or an operator, found `,` - --> $DIR/issue-93282.rs:2:9 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/issue-93282.rs:3:6 | LL | f<'a,> - | ^ expected one of 9 possible tokens + | ^ | -help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments +help: add `::` | LL | f::<'a,> | ++ error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/issue-93282.rs:13:11 + --> $DIR/issue-93282.rs:16:11 | LL | bar('y, x); | ^ expected `while`, `for`, `loop` or `{` after a label @@ -31,8 +20,25 @@ help: add `'` to close the char literal LL | bar('y', x); | + +error[E0261]: use of undeclared lifetime name `'a` + --> $DIR/issue-93282.rs:3:7 + | +LL | f<'a,> + | ^^ undeclared lifetime + | +help: consider introducing lifetime `'a` here + | +LL | fn main<'a>() { + | ++++ + +error[E0425]: cannot find value `f` in this scope + --> $DIR/issue-93282.rs:3:5 + | +LL | f<'a,> + | ^ not found in this scope + error[E0308]: mismatched types - --> $DIR/issue-93282.rs:13:9 + --> $DIR/issue-93282.rs:16:9 | LL | bar('y, x); | --- ^^ expected `usize`, found `char` @@ -40,11 +46,12 @@ LL | bar('y, x); | arguments to this function are incorrect | note: function defined here - --> $DIR/issue-93282.rs:7:4 + --> $DIR/issue-93282.rs:10:4 | LL | fn bar(a: usize, b: usize) -> usize { | ^^^ -------- -error: aborting due to 4 previous errors +error: aborting due to 5 previous errors -For more information about this error, try `rustc --explain E0308`. +Some errors have detailed explanations: E0261, E0308, E0425. +For more information about an error, try `rustc --explain E0261`. diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime-macro.rs b/tests/ui/parser/recover/missing-turbofish-lifetime-macro.rs new file mode 100644 index 0000000000000..366fc8ddfd982 --- /dev/null +++ b/tests/ui/parser/recover/missing-turbofish-lifetime-macro.rs @@ -0,0 +1,16 @@ +macro_rules! m { + ($e:expr) => { "expr" }; + (a < $l:lifetime >) => { "lifetime" }; +} + +fn test_macro() { + // Ensure turbofish recovery is disabled during macro matching to avoid spurious diagnostics. + // The second arm does NOT match; the expr-fragment error is a hard error. + let _ = m!(a < 'x >); + //~^ ERROR comparison operators cannot be chained + //~| HELP use `::<...>` instead of `<...>` + //~| HELP or use `(...)` + //~| ERROR expected `while`, `for`, `loop` or `{` after a label +} + +fn main() {} diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime-macro.stderr b/tests/ui/parser/recover/missing-turbofish-lifetime-macro.stderr new file mode 100644 index 0000000000000..a05d7e668a521 --- /dev/null +++ b/tests/ui/parser/recover/missing-turbofish-lifetime-macro.stderr @@ -0,0 +1,20 @@ +error: expected `while`, `for`, `loop` or `{` after a label + --> $DIR/missing-turbofish-lifetime-macro.rs:9:23 + | +LL | let _ = m!(a < 'x >); + | ^ expected `while`, `for`, `loop` or `{` after a label + +error: comparison operators cannot be chained + --> $DIR/missing-turbofish-lifetime-macro.rs:9:18 + | +LL | ($e:expr) => { "expr" }; + | ------- while parsing argument for this `expr` macro fragment +... +LL | let _ = m!(a < 'x >); + | ^ ^ + | + = help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + = help: or use `(...)` if you meant to specify fn arguments + +error: aborting due to 2 previous errors + diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime.fixed b/tests/ui/parser/recover/missing-turbofish-lifetime.fixed new file mode 100644 index 0000000000000..f5d8e2e63e6d5 --- /dev/null +++ b/tests/ui/parser/recover/missing-turbofish-lifetime.fixed @@ -0,0 +1,87 @@ +//! Regression test for +//! +//! Test that omitting the turbofish when passing a lifetime to an expression +//! emits a targeted suggestion rather than a cascading syntax error. + +#![allow(dead_code)] + +//@ run-rustfix + +struct Struct<'a> { + string: &'a str, +} + +fn struct_with_reserved_lifetime() { + let _ = Struct::<'_> { + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + string: "", + }; +} + +fn struct_with_named_lifetime<'a>() { + let _ = Struct::<'a> { + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + string: "", + }; +} + +fn struct_with_multichar_lifetime<'abc>() { + let _ = Struct::<'abc> { + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + string: "", + }; +} + +struct TupleStruct<'a>(&'a str); + +fn tuple_struct_with_reserved_lifetime() { + let _ = TupleStruct::<'_>(""); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` +} + +struct Wrapper<'a>(&'a str); +impl<'a> Wrapper<'a> { fn new(s: &'a str) -> Self { Self(s) } } + +struct Struct2<'a> { x: i32, _p: std::marker::PhantomData<&'a ()> } +impl<'a> Struct2<'a> { fn method(&self) -> Option<()> { Some(()) } } + +fn f<'a: 'a>() {} + +struct StructT<'a, T> { a: &'a str, b: T } +struct Struct3<'a, 'b> { a: &'a str, b: &'b str } + +fn chaining_cases<'a, 'b, T>() -> Option<()> { + let _ = Wrapper::<'a>::new("hi"); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + let _ = Struct2::<'a> { x: 1, _p: std::marker::PhantomData }.method(); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + let _ = Struct2::<'a> { x: 1, _p: std::marker::PhantomData }.method()?; + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + f::<'_>(); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + // struct with two lifetimes + let _ = Struct3::<'a, 'b> { a: "hi", b: "hi" }; + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + // struct with lifetime and type + let _ = StructT::<'a, &str> { a: "hi", b: "hi" }; + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + Some(()) +} + +fn main() {} diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime.rs b/tests/ui/parser/recover/missing-turbofish-lifetime.rs index 596b197a317da..1bc9e97878739 100644 --- a/tests/ui/parser/recover/missing-turbofish-lifetime.rs +++ b/tests/ui/parser/recover/missing-turbofish-lifetime.rs @@ -1,30 +1,87 @@ +//! Regression test for +//! +//! Test that omitting the turbofish when passing a lifetime to an expression +//! emits a targeted suggestion rather than a cascading syntax error. + +#![allow(dead_code)] + +//@ run-rustfix + struct Struct<'a> { string: &'a str, } fn struct_with_reserved_lifetime() { let _ = Struct<'_> { - //~^ ERROR labels cannot use keyword names - //~| ERROR expected `while`, `for`, `loop` or `{` after a label - //~| ERROR comparison operators cannot be chained + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` string: "", }; } -fn struct_with_named_lifetime() { +fn struct_with_named_lifetime<'a>() { let _ = Struct<'a> { - //~^ ERROR expected `while`, `for`, `loop` or `{` after a label - //~| ERROR comparison operators cannot be chained + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` string: "", }; } -fn struct_with_multichar_lifetime() { +fn struct_with_multichar_lifetime<'abc>() { let _ = Struct<'abc> { - //~^ ERROR expected `while`, `for`, `loop` or `{` after a label - //~| ERROR comparison operators cannot be chained + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` string: "", }; } +struct TupleStruct<'a>(&'a str); + +fn tuple_struct_with_reserved_lifetime() { + let _ = TupleStruct<'_>(""); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` +} + +struct Wrapper<'a>(&'a str); +impl<'a> Wrapper<'a> { fn new(s: &'a str) -> Self { Self(s) } } + +struct Struct2<'a> { x: i32, _p: std::marker::PhantomData<&'a ()> } +impl<'a> Struct2<'a> { fn method(&self) -> Option<()> { Some(()) } } + +fn f<'a: 'a>() {} + +struct StructT<'a, T> { a: &'a str, b: T } +struct Struct3<'a, 'b> { a: &'a str, b: &'b str } + +fn chaining_cases<'a, 'b, T>() -> Option<()> { + let _ = Wrapper<'a>::new("hi"); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + let _ = Struct2<'a> { x: 1, _p: std::marker::PhantomData }.method(); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + let _ = Struct2<'a> { x: 1, _p: std::marker::PhantomData }.method()?; + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + f<'_>(); + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + // struct with two lifetimes + let _ = Struct3<'a, 'b> { a: "hi", b: "hi" }; + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + // struct with lifetime and type + let _ = StructT<'a, &str> { a: "hi", b: "hi" }; + //~^ ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` + + Some(()) +} + fn main() {} diff --git a/tests/ui/parser/recover/missing-turbofish-lifetime.stderr b/tests/ui/parser/recover/missing-turbofish-lifetime.stderr index e14d9d6aa464c..faea5463bdfd4 100644 --- a/tests/ui/parser/recover/missing-turbofish-lifetime.stderr +++ b/tests/ui/parser/recover/missing-turbofish-lifetime.stderr @@ -1,57 +1,112 @@ -error: labels cannot use keyword names - --> $DIR/missing-turbofish-lifetime.rs:6:20 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:15:19 | LL | let _ = Struct<'_> { - | ^^ + | ^ + | +help: add `::` + | +LL | let _ = Struct::<'_> { + | ++ -error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/missing-turbofish-lifetime.rs:6:22 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:23:19 | -LL | let _ = Struct<'_> { - | ^ expected `while`, `for`, `loop` or `{` after a label +LL | let _ = Struct<'a> { + | ^ | -help: add `'` to close the char literal +help: add `::` | -LL | let _ = Struct<'_'> { - | + +LL | let _ = Struct::<'a> { + | ++ -error: comparison operators cannot be chained - --> $DIR/missing-turbofish-lifetime.rs:6:19 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:31:19 | -LL | let _ = Struct<'_> { - | ^ ^ +LL | let _ = Struct<'abc> { + | ^ + | +help: add `::` + | +LL | let _ = Struct::<'abc> { + | ++ -error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/missing-turbofish-lifetime.rs:15:22 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:41:24 | -LL | let _ = Struct<'a> { - | ^ expected `while`, `for`, `loop` or `{` after a label +LL | let _ = TupleStruct<'_>(""); + | ^ | -help: add `'` to close the char literal +help: add `::` | -LL | let _ = Struct<'a'> { - | + +LL | let _ = TupleStruct::<'_>(""); + | ++ -error: comparison operators cannot be chained - --> $DIR/missing-turbofish-lifetime.rs:15:19 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:58:20 | -LL | let _ = Struct<'a> { - | ^ ^ +LL | let _ = Wrapper<'a>::new("hi"); + | ^ + | +help: add `::` + | +LL | let _ = Wrapper::<'a>::new("hi"); + | ++ -error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/missing-turbofish-lifetime.rs:23:24 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:62:20 | -LL | let _ = Struct<'abc> { - | ^ expected `while`, `for`, `loop` or `{` after a label +LL | let _ = Struct2<'a> { x: 1, _p: std::marker::PhantomData }.method(); + | ^ + | +help: add `::` + | +LL | let _ = Struct2::<'a> { x: 1, _p: std::marker::PhantomData }.method(); + | ++ -error: comparison operators cannot be chained - --> $DIR/missing-turbofish-lifetime.rs:23:19 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:66:20 | -LL | let _ = Struct<'abc> { - | ^ ^ +LL | let _ = Struct2<'a> { x: 1, _p: std::marker::PhantomData }.method()?; + | ^ + | +help: add `::` + | +LL | let _ = Struct2::<'a> { x: 1, _p: std::marker::PhantomData }.method()?; + | ++ + +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:70:6 + | +LL | f<'_>(); + | ^ + | +help: add `::` + | +LL | f::<'_>(); + | ++ + +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:75:20 + | +LL | let _ = Struct3<'a, 'b> { a: "hi", b: "hi" }; + | ^ + | +help: add `::` + | +LL | let _ = Struct3::<'a, 'b> { a: "hi", b: "hi" }; + | ++ + +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/missing-turbofish-lifetime.rs:80:20 + | +LL | let _ = StructT<'a, &str> { a: "hi", b: "hi" }; + | ^ + | +help: add `::` | - = help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments - = help: or use `(...)` if you meant to specify fn arguments +LL | let _ = StructT::<'a, &str> { a: "hi", b: "hi" }; + | ++ -error: aborting due to 7 previous errors +error: aborting due to 10 previous errors diff --git a/tests/ui/parser/require-parens-for-chained-comparison.rs b/tests/ui/parser/require-parens-for-chained-comparison.rs index 6152fff6c0365..4162e9f27f36d 100644 --- a/tests/ui/parser/require-parens-for-chained-comparison.rs +++ b/tests/ui/parser/require-parens-for-chained-comparison.rs @@ -20,18 +20,14 @@ fn main() { //~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments let _ = f<'_, i8>(); - //~^ ERROR expected one of - //~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments - //~| ERROR expected - //~| HELP add `'` to close the char literal - //~| ERROR labels cannot use keyword names + //~^ ERROR cannot find function `f` in this scope + //~| ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` f<'_>(); - //~^ ERROR comparison operators cannot be chained - //~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments - //~| ERROR expected - //~| HELP add `'` to close the char literal - //~| ERROR labels cannot use keyword names + //~^ ERROR cannot find function `f` in this scope + //~| ERROR use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + //~| HELP add `::` let _ = f; //~^ ERROR comparison operators cannot be chained diff --git a/tests/ui/parser/require-parens-for-chained-comparison.stderr b/tests/ui/parser/require-parens-for-chained-comparison.stderr index 9edfae3625027..50f66927db118 100644 --- a/tests/ui/parser/require-parens-for-chained-comparison.stderr +++ b/tests/ui/parser/require-parens-for-chained-comparison.stderr @@ -53,64 +53,30 @@ help: use `::<...>` instead of `<...>` to specify lifetime, type, or const argum LL | let _ = f::(); | ++ -error: labels cannot use keyword names - --> $DIR/require-parens-for-chained-comparison.rs:22:15 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/require-parens-for-chained-comparison.rs:22:14 | LL | let _ = f<'_, i8>(); - | ^^ - -error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/require-parens-for-chained-comparison.rs:22:17 - | -LL | let _ = f<'_, i8>(); - | ^ expected `while`, `for`, `loop` or `{` after a label - | -help: add `'` to close the char literal + | ^ | -LL | let _ = f<'_', i8>(); - | + - -error: expected one of `.`, `:`, `;`, `?`, `else`, `for`, `loop`, `while`, or an operator, found `,` - --> $DIR/require-parens-for-chained-comparison.rs:22:17 - | -LL | let _ = f<'_, i8>(); - | ^ expected one of 9 possible tokens - | -help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments +help: add `::` | LL | let _ = f::<'_, i8>(); | ++ -error: labels cannot use keyword names - --> $DIR/require-parens-for-chained-comparison.rs:29:7 - | -LL | f<'_>(); - | ^^ - -error: expected `while`, `for`, `loop` or `{` after a label - --> $DIR/require-parens-for-chained-comparison.rs:29:9 +error: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + --> $DIR/require-parens-for-chained-comparison.rs:27:6 | LL | f<'_>(); - | ^ expected `while`, `for`, `loop` or `{` after a label + | ^ | -help: add `'` to close the char literal - | -LL | f<'_'>(); - | + - -error: comparison operators cannot be chained - --> $DIR/require-parens-for-chained-comparison.rs:29:6 - | -LL | f<'_>(); - | ^ ^ - | -help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments +help: add `::` | LL | f::<'_>(); | ++ error: comparison operators cannot be chained - --> $DIR/require-parens-for-chained-comparison.rs:36:14 + --> $DIR/require-parens-for-chained-comparison.rs:32:14 | LL | let _ = f; | ^ ^ @@ -118,5 +84,18 @@ LL | let _ = f; = help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments = help: or use `(...)` if you meant to specify fn arguments -error: aborting due to 12 previous errors +error[E0425]: cannot find function `f` in this scope + --> $DIR/require-parens-for-chained-comparison.rs:22:13 + | +LL | let _ = f<'_, i8>(); + | ^ not found in this scope + +error[E0425]: cannot find function `f` in this scope + --> $DIR/require-parens-for-chained-comparison.rs:27:5 + | +LL | f<'_>(); + | ^ not found in this scope + +error: aborting due to 10 previous errors +For more information about this error, try `rustc --explain E0425`. From eb85bf4a4e9e6fc4fb968ffb0f494b8ccec20328 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Oct 2026 16:12:38 +0200 Subject: [PATCH 11/27] [rustdoc] Improve display of `rustc_must_implement_one_of` note and generate links to the mentioned methods --- src/librustdoc/html/render/print_item.rs | 14 ++++++++++---- tests/rustdoc-html/impl/must_implement_one_of.rs | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index f6d5ada07f364..544d57d92613c 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -960,11 +960,17 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt: )?; if let Some(list) = must_implement_one_of_functions.as_deref() { - write!( - w, - "
At least one of the `{}` methods is required.
", - fmt::from_fn(|f| list.iter().joined("`, `", f)), + w.write_str( + "\ +
At least one of the ", )?; + for (pos, method) in list.iter().enumerate() { + if pos > 0 { + w.write_str(", ")?; + } + write!(w, "{method}")?; + } + w.write_str(" methods is required.
")?; } for m in required_methods { diff --git a/tests/rustdoc-html/impl/must_implement_one_of.rs b/tests/rustdoc-html/impl/must_implement_one_of.rs index ab965bab35667..2d905df0a4566 100644 --- a/tests/rustdoc-html/impl/must_implement_one_of.rs +++ b/tests/rustdoc-html/impl/must_implement_one_of.rs @@ -3,7 +3,7 @@ #[rustc_must_implement_one_of(a, b)] //@ matches c/trait.Trait.html '//*[@class="stab must_implement"]' \ -// 'At least one of the `a`, `b` methods is required.$' +// 'At least one of the a, b methods is required.$' pub trait Trait { fn a() {} fn b() {} From fe974855d46941397c22c5901457bd8d7fff1296 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Oct 2026 16:13:22 +0200 Subject: [PATCH 12/27] [rustdoc] Generate sidebar link for "required methods" even if there are none if the `rustc_must_implement_one_of` attribute is present --- src/librustdoc/html/render/sidebar.rs | 31 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 3befbf2cff8ff..e0bd1fc9792e6 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -337,30 +337,39 @@ fn sidebar_trait<'a>( ("provided-associated-consts", "Provided Associated Constants", prov_assoc_const), ("required-associated-types", "Required Associated Types", req_assoc), ("provided-associated-types", "Provided Associated Types", prov_assoc), - ("required-methods", "Required Methods", req_method), - ("provided-methods", "Provided Methods", prov_method), - ("foreign-impls", "Implementations on Foreign Types", foreign_impls), ] .into_iter() .map(|(id, title, items)| LinkBlock::new(Link::new(id, title), "", items)), ); + + let tcx = cx.tcx(); + // Because of `rustc_must_implement_one_of`, we might have a trait with no "required methods" + // but still want the link in the sidebar, so we handle it on its own. + let mut req_method_link = + LinkBlock::new(Link::new("required-methods", "Required Methods"), "", req_method); + let rustc_middle::ty::TraitDef { must_implement_one_of, .. } = tcx.trait_def(t.def_id); + req_method_link.force_render = must_implement_one_of.is_some(); + blocks.push(req_method_link); + + blocks.push(LinkBlock::new(Link::new("provided-methods", "Provided Methods"), "", prov_method)); + sidebar_assoc_items(cx, it, blocks, deref_id_map); - // Move the foreign impls block after dyn compatibility note to match the order of the headings - // in the main content. - let foreign_impls_block = blocks.pop_if(|b| b.heading.href == "foreign-impls"); - if !t.is_dyn_compatible(cx.tcx()) { + if !t.is_dyn_compatible(tcx) { blocks.push(LinkBlock::forced( Link::new("dyn-compatibility", "Dyn Compatibility"), "dyn-compatibility-note", )); } - if let Some(foreign_impls_block) = foreign_impls_block { - blocks.push(foreign_impls_block); - } + + blocks.push(LinkBlock::new( + Link::new("foreign-impls", "Implementations on Foreign Types"), + "", + foreign_impls, + )); blocks.push(LinkBlock::forced(Link::new("implementors", "Implementors"), "impl")); - if t.is_auto(cx.tcx()) { + if t.is_auto(tcx) { blocks.push(LinkBlock::forced( Link::new("synthetic-implementors", "Auto Implementors"), "impl-auto", From 9b08cd0a18c01f87433654d6f0f49cb97fc63189 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 13 Oct 2026 09:21:07 -0600 Subject: [PATCH 13/27] Windows FileExt trait doesn't need to handle ErrorKind::Interrupted; test improvements for same --- library/std/src/fs/tests.rs | 250 +++++++++++++++++++++---------- library/std/src/os/windows/fs.rs | 9 +- 2 files changed, 174 insertions(+), 85 deletions(-) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 7fb976d5ef3da..605ad25270a63 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -852,102 +852,175 @@ fn file_test_windows_fileext_trait_case_1() { #[test] #[cfg(windows)] fn file_test_windows_fileext_trait_case_2() { + use crate::cell::RefCell; use crate::os::windows::fs::FileExt; // Test when seek_read(), seek_write() return Ok(0) struct MockFile { expected_offset: u64, + read_calls: RefCell, + write_calls: RefCell, + } + + impl MockFile { + fn new(expected_offset: u64) -> Self { + Self { expected_offset, read_calls: RefCell::new(0), write_calls: RefCell::new(0) } + } } impl FileExt for MockFile { fn seek_read(&self, _buf: &mut [u8], offset: u64) -> io::Result { + *self.read_calls.borrow_mut() += 1; assert_eq!(offset, self.expected_offset); Ok(0) } fn seek_write(&self, _buf: &[u8], offset: u64) -> io::Result { + *self.write_calls.borrow_mut() += 1; assert_eq!(offset, self.expected_offset); Ok(0) } } - { - let mock_file = MockFile { expected_offset: 0 }; - let mut buf = [0; 256]; - assert_eq!( - mock_file.seek_read_exact(&mut buf, 0).unwrap_err().kind(), - io::ErrorKind::UnexpectedEof - ); - assert_eq!(mock_file.seek_write_all(&buf, 0).unwrap_err().kind(), io::ErrorKind::WriteZero); - } - - { - let mock_file = MockFile { expected_offset: 420 }; - let mut buf = [0; 256]; - assert_eq!( - mock_file.seek_read_exact(&mut buf, 420).unwrap_err().kind(), - io::ErrorKind::UnexpectedEof - ); - assert_eq!( - mock_file.seek_write_all(&buf, 420).unwrap_err().kind(), - io::ErrorKind::WriteZero - ); + let mut buf = [0; 256]; + for offset in [0, 1, 420] { + { + let mock_file = MockFile::new(offset); + assert_eq!( + mock_file.seek_read_exact(&mut buf, offset).unwrap_err().kind(), + ErrorKind::UnexpectedEof + ); + assert_eq!(*mock_file.read_calls.borrow(), 1); + assert_eq!(*mock_file.write_calls.borrow(), 0); + } + { + let mock_file = MockFile::new(offset); + assert_eq!( + mock_file.seek_write_all(&buf, offset).unwrap_err().kind(), + ErrorKind::WriteZero + ); + assert_eq!(*mock_file.read_calls.borrow(), 0); + assert_eq!(*mock_file.write_calls.borrow(), 1); + } } } #[test] #[cfg(windows)] fn file_test_windows_fileext_trait_case_3() { + use crate::cell::RefCell; use crate::os::windows::fs::FileExt; - // Test that Err other than io::ErrorKind::Interrupted are propagated up. + // Test that Err are propagated up. struct MockFile { expected_offset: u64, + read_calls: RefCell, + write_calls: RefCell, + } + + impl MockFile { + fn new(expected_offset: u64) -> Self { + Self { expected_offset, read_calls: RefCell::new(0), write_calls: RefCell::new(0) } + } } impl FileExt for MockFile { fn seek_read(&self, _buf: &mut [u8], offset: u64) -> io::Result { + *self.read_calls.borrow_mut() += 1; assert_eq!(offset, self.expected_offset); - Err(io::Error::new(io::ErrorKind::PermissionDenied, "seek_read")) + Err(io::Error::new(ErrorKind::PermissionDenied, "seek_read")) } fn seek_write(&self, _buf: &[u8], offset: u64) -> io::Result { + *self.write_calls.borrow_mut() += 1; assert_eq!(offset, self.expected_offset); - Err(io::Error::new(io::ErrorKind::ConnectionRefused, "seek_write")) + Err(io::Error::new(ErrorKind::ConnectionRefused, "seek_write")) } } - { - let mock_file = MockFile { expected_offset: 0 }; - let mut buf = [0; 256]; - assert_eq!( - mock_file.seek_read_exact(&mut buf, 0).unwrap_err().kind(), - io::ErrorKind::PermissionDenied - ); - assert_eq!( - mock_file.seek_write_all(&buf, 0).unwrap_err().kind(), - io::ErrorKind::ConnectionRefused - ); + let mut buf = [0; 256]; + for offset in [0, 1, 420] { + { + let mock_file = MockFile::new(offset); + assert_eq!( + mock_file.seek_read_exact(&mut buf, offset).unwrap_err().kind(), + ErrorKind::PermissionDenied + ); + assert_eq!(*mock_file.read_calls.borrow(), 1); + assert_eq!(*mock_file.write_calls.borrow(), 0); + } + { + let mock_file = MockFile::new(offset); + assert_eq!( + mock_file.seek_write_all(&buf, offset).unwrap_err().kind(), + ErrorKind::ConnectionRefused + ); + assert_eq!(*mock_file.read_calls.borrow(), 0); + assert_eq!(*mock_file.write_calls.borrow(), 1); + } } +} - { - let mock_file = MockFile { expected_offset: 420 }; - let mut buf = [0; 256]; - assert_eq!( - mock_file.seek_read_exact(&mut buf, 420).unwrap_err().kind(), - io::ErrorKind::PermissionDenied - ); - assert_eq!( - mock_file.seek_write_all(&buf, 420).unwrap_err().kind(), - io::ErrorKind::ConnectionRefused - ); +#[test] +#[cfg(windows)] +fn file_test_windows_fileext_trait_case_4() { + use crate::cell::RefCell; + use crate::os::windows::fs::FileExt; + + // Test that Err ErrorKind::Interrupted are propagated up (unlike Unix versions) + struct MockFile { + expected_offset: u64, + read_calls: RefCell, + write_calls: RefCell, + } + + impl MockFile { + fn new(expected_offset: u64) -> Self { + Self { expected_offset, read_calls: RefCell::new(0), write_calls: RefCell::new(0) } + } + } + + impl FileExt for MockFile { + fn seek_read(&self, _buf: &mut [u8], offset: u64) -> io::Result { + *self.read_calls.borrow_mut() += 1; + assert_eq!(offset, self.expected_offset); + Err(io::Error::new(ErrorKind::Interrupted, "seek_read")) + } + + fn seek_write(&self, _buf: &[u8], offset: u64) -> io::Result { + *self.write_calls.borrow_mut() += 1; + assert_eq!(offset, self.expected_offset); + Err(io::Error::new(ErrorKind::Interrupted, "seek_write")) + } + } + + let mut buf = [0; 256]; + for offset in [0, 1, 420] { + { + let mock_file = MockFile::new(offset); + assert_eq!( + mock_file.seek_read_exact(&mut buf, offset).unwrap_err().kind(), + ErrorKind::Interrupted + ); + assert_eq!(*mock_file.read_calls.borrow(), 1); + assert_eq!(*mock_file.write_calls.borrow(), 0); + } + { + let mock_file = MockFile::new(offset); + assert_eq!( + mock_file.seek_write_all(&buf, offset).unwrap_err().kind(), + ErrorKind::Interrupted + ); + assert_eq!(*mock_file.read_calls.borrow(), 0); + assert_eq!(*mock_file.write_calls.borrow(), 1); + } } - // FIXME: Cover io::ErrorKind::Interrupted, but don't infinite loop ;) } #[test] #[cfg(windows)] -fn file_test_windows_fileext_trait_case_4() { +fn file_test_windows_fileext_trait_case_5() { + use crate::cell::RefCell; use crate::os::windows::fs::FileExt; const MSG: &[u8] = @@ -957,10 +1030,19 @@ fn file_test_windows_fileext_trait_case_4() { // seek_write(), respectively. struct MockFile { expected_offset: u64, + read_calls: RefCell, + write_calls: RefCell, + } + + impl MockFile { + fn new(expected_offset: u64) -> Self { + Self { expected_offset, read_calls: RefCell::new(0), write_calls: RefCell::new(0) } + } } impl FileExt for MockFile { fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result { + *self.read_calls.borrow_mut() += 1; assert_eq!(offset, self.expected_offset); assert_eq!(buf.len(), MSG.len()); assert_eq!(buf, &[0; MSG.len()]); @@ -969,6 +1051,7 @@ fn file_test_windows_fileext_trait_case_4() { } fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result { + *self.write_calls.borrow_mut() += 1; assert_eq!(offset, self.expected_offset); assert_eq!(buf.len(), MSG.len()); assert_eq!(buf, MSG); @@ -976,26 +1059,28 @@ fn file_test_windows_fileext_trait_case_4() { } } - { - let mock_file = MockFile { expected_offset: 0 }; - let mut buf = [0; MSG.len()]; - check!(mock_file.seek_read_exact(&mut buf, 0)); - assert_eq!(&buf, MSG); - check!(mock_file.seek_write_all(&buf, 0)); - } - - { - let mock_file = MockFile { expected_offset: 420 }; - let mut buf = [0; MSG.len()]; - check!(mock_file.seek_read_exact(&mut buf, 420)); - assert_eq!(&buf, MSG); - check!(mock_file.seek_write_all(&buf, 420)); + for offset in [0, 1, 420] { + { + let mock_file = MockFile::new(offset); + let mut buf = [0; MSG.len()]; + check!(mock_file.seek_read_exact(&mut buf, offset)); + assert_eq!(&buf, MSG); + assert_eq!(*mock_file.read_calls.borrow(), 1); + assert_eq!(*mock_file.write_calls.borrow(), 0); + } + { + let mock_file = MockFile::new(offset); + check!(mock_file.seek_write_all(MSG, offset)); + assert_eq!(*mock_file.read_calls.borrow(), 0); + assert_eq!(*mock_file.write_calls.borrow(), 1); + } } } #[test] #[cfg(windows)] -fn file_test_windows_fileext_trait_case_5() { +fn file_test_windows_fileext_trait_case_6() { + use crate::cell::RefCell; use crate::os::windows::fs::FileExt; const MSG: &[u8] = @@ -1004,36 +1089,47 @@ fn file_test_windows_fileext_trait_case_5() { // Test pathological case where seek_read(), seek_write() only do 1 byte per call, return Ok(1) struct MockFile { base_offset: u64, + read_calls: RefCell, + write_calls: RefCell, + } + + impl MockFile { + fn new(base_offset: u64) -> Self { + Self { base_offset, read_calls: RefCell::new(0), write_calls: RefCell::new(0) } + } } impl FileExt for MockFile { fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result { + *self.read_calls.borrow_mut() += 1; let offset = (offset - self.base_offset) as usize; buf[0..1].copy_from_slice(&MSG[offset..offset + 1]); Ok(1) } fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result { + *self.write_calls.borrow_mut() += 1; let offset = (offset - self.base_offset) as usize; assert_eq!(buf[0..1], MSG[offset..offset + 1]); Ok(1) } } - { - let mock_file = MockFile { base_offset: 0 }; - let mut buf = [0; MSG.len()]; - check!(mock_file.seek_read_exact(&mut buf, 0)); - assert_eq!(&buf, MSG); - check!(mock_file.seek_write_all(&buf, 0)); - } - - { - let mock_file = MockFile { base_offset: 420 }; - let mut buf = [0; MSG.len()]; - check!(mock_file.seek_read_exact(&mut buf, 420)); - assert_eq!(&buf, MSG); - check!(mock_file.seek_write_all(&buf, 420)); + for offset in [0, 1, 420] { + { + let mock_file = MockFile::new(offset); + let mut buf = [0; MSG.len()]; + check!(mock_file.seek_read_exact(&mut buf, offset)); + assert_eq!(&buf, MSG); + assert_eq!(*mock_file.read_calls.borrow(), MSG.len()); + assert_eq!(*mock_file.write_calls.borrow(), 0); + } + { + let mock_file = MockFile::new(offset); + check!(mock_file.seek_write_all(MSG, offset)); + assert_eq!(*mock_file.read_calls.borrow(), 0); + assert_eq!(*mock_file.write_calls.borrow(), MSG.len()); + } } } diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index c10766470e873..4c2506ffae66c 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -69,9 +69,6 @@ pub trait FileExt { /// /// # Errors /// - /// If this function encounters an error of the kind - /// [`io::ErrorKind::Interrupted`] then the error is ignored and the operation - /// will continue. /// /// If this function encounters an "end of file" before completely filling /// the buffer, it returns an error of the kind [`io::ErrorKind::UnexpectedEof`]. @@ -113,7 +110,6 @@ pub trait FileExt { buf = &mut buf[n..]; offset += n as u64; } - Err(ref e) if e.is_interrupted() => {} Err(e) => return Err(e), } } @@ -203,7 +199,6 @@ pub trait FileExt { let prev_written = buf.written(); match self.seek_read_buf(buf.reborrow(), offset) { Ok(()) => {} - Err(e) if e.is_interrupted() => {} Err(e) => return Err(e), } let n = buf.written() - prev_written; @@ -264,8 +259,7 @@ pub trait FileExt { /// /// # Errors /// - /// This function will return the first error of - /// non-[`io::ErrorKind::Interrupted`] kind that [`seek_write`] returns. + /// This function will return the first error that [`seek_write`] returns. /// /// [`seek_write`]: FileExt::seek_write /// @@ -298,7 +292,6 @@ pub trait FileExt { buf = &buf[n..]; offset += n as u64 } - Err(ref e) if e.is_interrupted() => {} Err(e) => return Err(e), } } From fca5ba904c8fbba5be52e5e034b2339f893114b8 Mon Sep 17 00:00:00 2001 From: Viktor Kuksgauzen <47536339+Kuksgauzen@users.noreply.github.com> Date: Tue, 13 Oct 2026 20:41:55 +0400 Subject: [PATCH 14/27] Add regression test for higher-ranked param env normalization ICE --- ...r-ranked-param-env-normalization-155977.rs | 19 +++++++++++++++++++ ...nked-param-env-normalization-155977.stderr | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.rs create mode 100644 tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.stderr diff --git a/tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.rs b/tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.rs new file mode 100644 index 0000000000000..0d45d1ce9b1c4 --- /dev/null +++ b/tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.rs @@ -0,0 +1,19 @@ +//@ compile-flags: -Znext-solver + +// Regression test for #155977: the new solver used to ICE while normalizing +// a higher-ranked where-clause in the param env. +// Fixed by eager param-env normalization in #156976. + +trait Trait { + type Output; +} + +fn walk2() +//~^ ERROR the trait bound +where + for<'a> F: 'a, + for<'a> >::Output: 'a, +{ +} + +fn main() {} diff --git a/tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.stderr b/tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.stderr new file mode 100644 index 0000000000000..1889e3be86aba --- /dev/null +++ b/tests/ui/traits/next-solver/normalize/higher-ranked-param-env-normalization-155977.stderr @@ -0,0 +1,18 @@ +error[E0277]: the trait bound `for<'a> F: Trait<&'a u32>` is not satisfied + --> $DIR/higher-ranked-param-env-normalization-155977.rs:11:1 + | +LL | / fn walk2() +LL | | +LL | | where +LL | | for<'a> F: 'a, +LL | | for<'a> >::Output: 'a, + | |______________________________________________^ the trait `for<'a> Trait<&'a u32>` is not implemented for `F` + | +help: consider further restricting type parameter `F` with trait `Trait` + | +LL | for<'a> F: 'a + for<'a> Trait<&'a u32>, + | ++++++++++++++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. From 5448cc68ec6ed5bf2e1496f018143630eaacb615 Mon Sep 17 00:00:00 2001 From: Francisco Gouveia Date: Tue, 13 Oct 2026 18:28:52 +0100 Subject: [PATCH 15/27] Add a test for the `rustc_show_spans` attribute use in macros --- tests/ui/attributes/show_spans_macros.rs | 44 ++++++++++++++++++++ tests/ui/attributes/show_spans_macros.stderr | 18 ++++++++ 2 files changed, 62 insertions(+) create mode 100644 tests/ui/attributes/show_spans_macros.rs create mode 100644 tests/ui/attributes/show_spans_macros.stderr diff --git a/tests/ui/attributes/show_spans_macros.rs b/tests/ui/attributes/show_spans_macros.rs new file mode 100644 index 0000000000000..a8a435b009e16 --- /dev/null +++ b/tests/ui/attributes/show_spans_macros.rs @@ -0,0 +1,44 @@ +// Illustrates the behavior of the `rustc_show_spans` internal attribute. +// It shows how the parser combines spans of nodes built from tokens of +// different macro contexts. +// +// The objective is for a node's span to be a chain over its internal +// members and not only from first to last. That is, we should converge +// to `span(first).to(span(second))...to(span(last))` instead of just +// `span(first).to(span(last))`. This way we can report spans inside +// macro definitions when the children come from different contexts. + +//@ check-pass +#![feature(rustc_attrs)] + +mod foo { + pub const BAR: u8 = 1; +} + +// FIXME(#126763): the span should be `$a::$b` in the macro body. +macro_rules! path { + ($a:tt, $b:tt) => { + let _ = $a::$b; + }; +} + +#[rustc_show_spans(path)] +fn path_from_tts() { + path!(foo, BAR); +} + +// FIXME(#126763): the field access span should be `$a.$f` in the macro body. +macro_rules! field { + ($a:tt, $f:tt) => { + let _ = $a.$f; + }; +} + +const T: (u8,) = (1,); + +#[rustc_show_spans(expr)] +fn field_access() { + field!(T, 0); +} + +fn main() {} diff --git a/tests/ui/attributes/show_spans_macros.stderr b/tests/ui/attributes/show_spans_macros.stderr new file mode 100644 index 0000000000000..0fa76e9cc9392 --- /dev/null +++ b/tests/ui/attributes/show_spans_macros.stderr @@ -0,0 +1,18 @@ +note: showing the spans of the path + --> $DIR/show_spans_macros.rs:27:11 + | +LL | path!(foo, BAR); + | ^^^^^^^^ + +note: showing the spans of the expr + --> $DIR/show_spans_macros.rs:41:12 + | +LL | field!(T, 0); + | ^^^^ + +note: showing the spans of the expr + --> $DIR/show_spans_macros.rs:41:12 + | +LL | field!(T, 0); + | ^ + From 29e3a087d013e13dab3007e3556aab5402e1e61f Mon Sep 17 00:00:00 2001 From: Francisco Gouveia Date: Tue, 13 Oct 2026 18:01:20 +0100 Subject: [PATCH 16/27] Add a test for the `rustc_show_spans` attribute on nested functions --- .../ui/attributes/show_spans-nested-items.rs | 28 +++++++++++++++++++ .../attributes/show_spans-nested-items.stderr | 12 ++++++++ 2 files changed, 40 insertions(+) create mode 100644 tests/ui/attributes/show_spans-nested-items.rs create mode 100644 tests/ui/attributes/show_spans-nested-items.stderr diff --git a/tests/ui/attributes/show_spans-nested-items.rs b/tests/ui/attributes/show_spans-nested-items.rs new file mode 100644 index 0000000000000..52ff359ebd129 --- /dev/null +++ b/tests/ui/attributes/show_spans-nested-items.rs @@ -0,0 +1,28 @@ +// Illustrates the behavior of the `rustc_show_spans` internal attribute +// in the case of nested functions. + +//@ check-pass +#![feature(rustc_attrs)] + +#[rustc_show_spans(expr)] +fn outer() { + // No note because it's a nested function. + fn inner() { + let _ = 0; + } + + // The span will be over the `ty` (i.e, `u8`), not the `expr` (i.e., `1`). + #[rustc_show_spans(ty)] + fn inner_annotated() { + let _: u8 = 1; //~ NOTE showing the spans of the ty + } + + let _: u8 = 2; //~ NOTE showing the spans of the expr +} + +// No note as the annotation of the previous function does not affect this one. +fn not_annotated() { + let _ = 3; +} + +fn main() {} diff --git a/tests/ui/attributes/show_spans-nested-items.stderr b/tests/ui/attributes/show_spans-nested-items.stderr new file mode 100644 index 0000000000000..c1ca87bbce003 --- /dev/null +++ b/tests/ui/attributes/show_spans-nested-items.stderr @@ -0,0 +1,12 @@ +note: showing the spans of the ty + --> $DIR/show_spans-nested-items.rs:17:16 + | +LL | let _: u8 = 1; + | ^^ + +note: showing the spans of the expr + --> $DIR/show_spans-nested-items.rs:20:17 + | +LL | let _: u8 = 2; + | ^ + From cad2ab9ffe4878410152581c90db2b42b4a1339c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Oct 2026 16:26:46 +0200 Subject: [PATCH 17/27] Add regression test for `rustc_must_implement_one_of` changes --- .../trait-rustc_must_implement_one_of.rs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/rustdoc-html/trait-rustc_must_implement_one_of.rs diff --git a/tests/rustdoc-html/trait-rustc_must_implement_one_of.rs b/tests/rustdoc-html/trait-rustc_must_implement_one_of.rs new file mode 100644 index 0000000000000..57accd9fa0791 --- /dev/null +++ b/tests/rustdoc-html/trait-rustc_must_implement_one_of.rs @@ -0,0 +1,50 @@ +// This test ensures a few things around the `rustc_must_implement_one_of` attribute: +// 1. The listed methods are links +// 2. It gets a heading in the sidebar even if there is no "required methods" +// Regression test for . + +#![allow(internal_features)] +#![feature(rustc_attrs)] + +#![crate_name = "foo"] + +//@ has 'foo/trait.Read.html' +//@ has - '//*[@class="stab must_implement"]/a[@href="#method.read"]' 'read' +//@ has - '//*[@class="stab must_implement"]/a[@href="#method.read_buf"]' 'read_buf' + +// There should be 3 headings in the sidebar: Required/Provided methods and "Implementors". +//@ count - '//*[@id="rustdoc-toc"]/h3' 3 +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#required-methods"]' 'Required Methods' +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#provided-methods"]' 'Provided Methods' +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#implementors"]' 'Implementors' + +#[rustc_must_implement_one_of(read_buf, read)] +pub trait Read { + fn read_buf(&self) {} + fn read(&self) {} +} + +//@ has 'foo/trait.Tadam.html' +// There should still be 3 headings in the sidebar: Required/Provided methods and "Implementors". +//@ count - '//*[@id="rustdoc-toc"]/h3' 3 +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#required-methods"]' 'Required Methods' +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#provided-methods"]' 'Provided Methods' +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#implementors"]' 'Implementors' + +#[rustc_must_implement_one_of(read_buf, read)] +pub trait Tadam { + fn read_buf(&self) {} + fn read(&self) {} + fn foo(&self) {} +} + +//@ has 'foo/trait.Other.html' +// There should be 2 headings in the sidebar: Provided methods and "Implementors". +//@ count - '//*[@id="rustdoc-toc"]/h3' 2 +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#provided-methods"]' 'Provided Methods' +//@ has - '//*[@id="rustdoc-toc"]/h3/a[@href="#implementors"]' 'Implementors' + +pub trait Other { + fn read_buf(&self) {} + fn read(&self) {} +} From 6ffc09af469680d468d12b0f6273e9386a9f11d5 Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:07:08 +0330 Subject: [PATCH 18/27] Add regression test for hang in overlap check with GAT alias --- .../overlap-check-gat-alias-hang-151503.rs | 29 +++++++++++++++++++ ...overlap-check-gat-alias-hang-151503.stderr | 11 +++++++ 2 files changed, 40 insertions(+) create mode 100644 tests/ui/coherence/overlap-check-gat-alias-hang-151503.rs create mode 100644 tests/ui/coherence/overlap-check-gat-alias-hang-151503.stderr diff --git a/tests/ui/coherence/overlap-check-gat-alias-hang-151503.rs b/tests/ui/coherence/overlap-check-gat-alias-hang-151503.rs new file mode 100644 index 0000000000000..21d6342dedb72 --- /dev/null +++ b/tests/ui/coherence/overlap-check-gat-alias-hang-151503.rs @@ -0,0 +1,29 @@ +//! Regression test for . +//! +//! The overlap check for the two `MyFrom` impls used to hang. + +trait HasAlias {} +struct Dummy; +trait DummyTrait { + type DummyType; +} +impl DummyTrait for Dummy { + type DummyType = T; +} +type AliasOf = ::DummyType; +struct Matrix(T, S); +type OMatrix = Matrix>; + +trait SimdValue { + type Element; +} +impl SimdValue for OMatrix { + type Element = OMatrix; +} +trait MyFrom {} +impl MyFrom for T {} +impl MyFrom for OMatrix {} +//~^ ERROR conflicting implementations of trait `MyFrom>` for type `Matrix<_, _>` +impl HasAlias for Matrix {} + +fn main() {} diff --git a/tests/ui/coherence/overlap-check-gat-alias-hang-151503.stderr b/tests/ui/coherence/overlap-check-gat-alias-hang-151503.stderr new file mode 100644 index 0000000000000..a20b12334f2fc --- /dev/null +++ b/tests/ui/coherence/overlap-check-gat-alias-hang-151503.stderr @@ -0,0 +1,11 @@ +error[E0119]: conflicting implementations of trait `MyFrom>` for type `Matrix<_, _>` + --> $DIR/overlap-check-gat-alias-hang-151503.rs:25:1 + | +LL | impl MyFrom for T {} + | ----------------------- first implementation here +LL | impl MyFrom for OMatrix {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Matrix<_, _>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0119`. From c0976820bf9638c2bb10d2d12f5a9cc31a487b8b Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 6 Oct 2026 16:47:03 +1100 Subject: [PATCH 19/27] Remove the `span_use_eq_ctxt` lint PR 163857 changed the span representation and reduced `eq_ctxt` to a trivial equality check, which means `span_a.ctxt() == span_b.ctxt()` is now an ok alternative to `span_a.eq_ctxt(span_b)`. This commit removes `span_use_eq_ctxt` because it's no longer necessary. `eq_ctxt` remains because removing it would cause some churn; it might be worth doing as a follow-up. --- compiler/rustc_lint/src/diagnostics.rs | 4 -- compiler/rustc_lint/src/internal.rs | 39 +------------------ compiler/rustc_lint/src/lib.rs | 3 -- compiler/rustc_span/src/span_encoding.rs | 9 +---- compiler/rustc_span/src/symbol.rs | 1 - .../internal-lints/span_use_eq_ctxt.rs | 13 ------- .../internal-lints/span_use_eq_ctxt.stderr | 14 ------- 7 files changed, 4 insertions(+), 79 deletions(-) delete mode 100644 tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.rs delete mode 100644 tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.stderr diff --git a/compiler/rustc_lint/src/diagnostics.rs b/compiler/rustc_lint/src/diagnostics.rs index 601f1213650e1..488de4b6c7f95 100644 --- a/compiler/rustc_lint/src/diagnostics.rs +++ b/compiler/rustc_lint/src/diagnostics.rs @@ -1231,10 +1231,6 @@ pub(crate) struct QueryUntracked { pub method: Symbol, } -#[derive(Diagnostic)] -#[diag("use `.eq_ctxt()` instead of `.ctxt() == .ctxt()`")] -pub(crate) struct SpanUseEqCtxtDiag; - #[derive(Diagnostic)] #[diag("using `{$fn_name}` on a string literal")] #[help("consider adding the symbol to `compiler/rustc_span/src/symbol.rs`")] diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index bce726311e51f..03622e253c90a 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -20,8 +20,8 @@ use crate::diagnostics::{ AttributeKindInFindAttr, BadOptAccessDiag, DefaultHashTypesDiag, ImplicitSysrootCrateImportDiag, LintPassByHand, MissingGenericTypeVisitableDeriveDiag, NonGlobImportTypeIrInherent, QueryInstability, QueryUntracked, - RustcMustMatchExhaustivelyNotExhaustive, SpanUseEqCtxtDiag, SymbolInternStringLiteralDiag, - TyQualified, TykindDiag, TykindKind, TypeIrDirectUse, TypeIrInherentUsage, TypeIrTraitUsage, + RustcMustMatchExhaustivelyNotExhaustive, SymbolInternStringLiteralDiag, TyQualified, + TykindDiag, TykindKind, TypeIrDirectUse, TypeIrInherentUsage, TypeIrTraitUsage, }; use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; @@ -570,41 +570,6 @@ impl LateLintPass<'_> for BadOptAccess { } } -declare_tool_lint! { - pub rustc::SPAN_USE_EQ_CTXT, - Allow, - "forbid uses of `==` with `Span::ctxt`, suggest `Span::eq_ctxt` instead", - report_in_external_macro: true -} - -declare_lint_pass!(SpanUseEqCtxt => [SPAN_USE_EQ_CTXT]); - -impl<'tcx> LateLintPass<'tcx> for SpanUseEqCtxt { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) { - if let hir::ExprKind::Binary( - hir::BinOp { node: hir::BinOpKind::Eq | hir::BinOpKind::Ne, .. }, - lhs, - rhs, - ) = expr.kind - { - if is_span_ctxt_call(cx, lhs) && is_span_ctxt_call(cx, rhs) { - cx.emit_span_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag); - } - } - } -} - -fn is_span_ctxt_call(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - match &expr.kind { - hir::ExprKind::MethodCall(..) => cx - .typeck_results() - .type_dependent_def_id(expr.hir_id) - .is_some_and(|call_did| cx.tcx.is_diagnostic_item(sym::SpanCtxt, call_did)), - - _ => false, - } -} - declare_tool_lint! { /// The `symbol_intern_string_literal` lint detects `Symbol::intern` or `Ident::from_str_and_span` being called on a string literal pub rustc::SYMBOL_INTERN_STRING_LITERAL, diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 88d99449ba879..94f6183740954 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -301,7 +301,6 @@ late_lint_methods!( MissingGenericTypeVisitableDerive: MissingGenericTypeVisitableDerive, QueryStability: QueryStability, RustcMustMatchExhaustively: RustcMustMatchExhaustively, - SpanUseEqCtxt: SpanUseEqCtxt, SymbolInternStringLiteral: SymbolInternStringLiteral, TyTyKind: TyTyKind, TypeIr: TypeIr, @@ -759,8 +758,6 @@ fn register_internals(store: &mut LintStore) { LintId::of(BAD_OPT_ACCESS), // Late pass: DisallowedPassByRef LintId::of(DISALLOWED_PASS_BY_REF), - // Late pass: SpanUseEqCtxt - LintId::of(SPAN_USE_EQ_CTXT), // Late pass: SymbolInternStringLiteral // Note: this one is not included in rustc::internal because rustc_driver crates // outside the compiler can't/shouldn't add preinterned symbols. For rustc itself, diff --git a/compiler/rustc_span/src/span_encoding.rs b/compiler/rustc_span/src/span_encoding.rs index f4cba4384d589..b036e2de05b02 100644 --- a/compiler/rustc_span/src/span_encoding.rs +++ b/compiler/rustc_span/src/span_encoding.rs @@ -479,7 +479,6 @@ impl Span { /// This function is used as a fast path when decoding the full `SpanData` is not necessary. /// It's a cut-down version of `data_untracked` and doesn't require taking the interner lock. - #[cfg_attr(not(test), rustc_diagnostic_item = "SpanCtxt")] #[inline] pub fn ctxt(self) -> SyntaxContext { let ctxt32 = match_span_kind! { @@ -492,12 +491,8 @@ impl Span { SyntaxContext::from_u32(ctxt32) } - // Allow `span_use_eq_ctxt` because this is `eq_ctxt`! - // - // FIXME(nnethercote): context equality used to be much more complex, but the span - // representation changed and now simple equality is fine. This method and the - // `span_use_eq_ctxt` lint and the `SpanCtxt` diagnostic item can be removed. - #[allow(rustc::span_use_eq_ctxt)] + // With the pre-#163857 span representation this method was more complex and required. Now it's + // trivial but it hasn't been removed simply to avoid churn. #[inline] pub fn eq_ctxt(self, other: Span) -> bool { self.ctxt() == other.ctxt() diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 0f6a0b7012ad7..0422fd6658661 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -333,7 +333,6 @@ symbols! { SliceIndex, Some, Source, - SpanCtxt, StdinLock, Str, String, diff --git a/tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.rs b/tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.rs deleted file mode 100644 index 369c2dd9c4877..0000000000000 --- a/tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Test the `rustc::span_use_eq_ctxt` internal lint -//@ compile-flags: -Z unstable-options - -#![feature(rustc_private)] -#![deny(rustc::span_use_eq_ctxt)] -#![crate_type = "lib"] - -extern crate rustc_span; -use rustc_span::Span; - -pub fn f(s: Span, t: Span) -> bool { - s.ctxt() == t.ctxt() //~ ERROR use `.eq_ctxt()` instead of `.ctxt() == .ctxt()` -} diff --git a/tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.stderr b/tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.stderr deleted file mode 100644 index b52df0368b16d..0000000000000 --- a/tests/ui-fulldeps/internal-lints/span_use_eq_ctxt.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error: use `.eq_ctxt()` instead of `.ctxt() == .ctxt()` - --> $DIR/span_use_eq_ctxt.rs:12:5 - | -LL | s.ctxt() == t.ctxt() - | ^^^^^^^^^^^^^^^^^^^^ - | -note: the lint level is defined here - --> $DIR/span_use_eq_ctxt.rs:5:9 - | -LL | #![deny(rustc::span_use_eq_ctxt)] - | ^^^^^^^^^^^^^^^^^^^^^^^ - -error: aborting due to 1 previous error - From 9c1eb804990d9cbfe3d763a275958aab1bb0dd0f Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 14 Oct 2026 02:30:42 +0200 Subject: [PATCH 20/27] Prefer WriteConsoleW for stdout on Windows This drops the `GetConsoleOutputCP` call, which would otherwise do an RPC to the console server (= slow). This leaves us with `is_console` which was using another RPC via `GetConsoleMode` (= doubly slow). On Windows 8+ we can just use `NtQueryVolumeInformationFile()` to test for `FILE_DEVICE_CONSOLE`, resulting in a ~2x console stdout speedup. --- library/std/src/sys/io/is_terminal/windows.rs | 48 +++++++++++++---- library/std/src/sys/pal/windows/c.rs | 7 +-- .../std/src/sys/pal/windows/c/bindings.txt | 4 +- .../std/src/sys/pal/windows/c/windows_sys.rs | 10 +++- library/std/src/sys/stdio/windows.rs | 53 ++++++++++++------- .../miri/src/shims/windows/foreign_items.rs | 28 ++++------ 6 files changed, 98 insertions(+), 52 deletions(-) diff --git a/library/std/src/sys/io/is_terminal/windows.rs b/library/std/src/sys/io/is_terminal/windows.rs index b0c718d71f9f3..6d66e93087911 100644 --- a/library/std/src/sys/io/is_terminal/windows.rs +++ b/library/std/src/sys/io/is_terminal/windows.rs @@ -1,4 +1,5 @@ use crate::ffi::c_void; +use crate::mem::MaybeUninit; use crate::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle}; use crate::sys::c; @@ -12,10 +13,44 @@ fn handle_is_console(handle: BorrowedHandle<'_>) -> bool { return false; } - let mut out = 0; - if unsafe { c::GetConsoleMode(handle.as_raw_handle(), &mut out) != 0 } { - // False positives aren't possible. If we got a console then we definitely have a console. - return true; + cfg_select! { + target_vendor = "win7" => { + let mut out = 0; + if unsafe { c::GetConsoleMode(handle.as_raw_handle(), &mut out) != 0 } { + // False positives aren't possible. If we got a console then we definitely have a console. + return true; + } + + // Early return if the handle is not FILE_TYPE_PIPE, implying it cannot be an msys PTY. + if unsafe { c::GetFileType(handle.as_raw_handle()) != 0x0003 } { + return false; + } + } + _ => unsafe { + // `GetConsoleMode` incurs a remote RPC round-trip (= bad), so it's best avoided. + // `NtQueryVolumeInformationFile` lets us test the handle directly. + // The required `FILE_DEVICE_CONSOLE` type was introduced in Windows 8. + // Fun fact: `GetFileType` uses `NtQueryVolumeInformationFile` internally. + let mut io_status = MaybeUninit::::uninit(); + let mut device_info = MaybeUninit::::uninit(); + let status = c::NtQueryVolumeInformationFile( + handle.as_raw_handle(), + io_status.as_mut_ptr(), + device_info.as_mut_ptr().cast(), + size_of_val(&device_info) as u32, + c::FileFsDeviceInformation, + ); + if status < 0 { + return false; + } + + match device_info.assume_init_ref().DeviceType { + c::FILE_DEVICE_CONSOLE => return true, + // If it's a named pipe, it may be an msys PTY. + c::FILE_DEVICE_NAMED_PIPE => {} // aka FILE_TYPE_PIPE + _ => return false, + } + }, } // Otherwise, we fall back to an msys hack to see if we can detect the presence of a pty. @@ -23,11 +58,6 @@ fn handle_is_console(handle: BorrowedHandle<'_>) -> bool { } fn msys_tty_on(handle: BorrowedHandle<'_>) -> bool { - // Early return if the handle is not a pipe. - if unsafe { c::GetFileType(handle.as_raw_handle()) != c::FILE_TYPE_PIPE } { - return false; - } - /// Mirrors [`FILE_NAME_INFO`], giving it a fixed length that we can stack /// allocate /// diff --git a/library/std/src/sys/pal/windows/c.rs b/library/std/src/sys/pal/windows/c.rs index 25031968a7e02..67345d183a2d2 100644 --- a/library/std/src/sys/pal/windows/c.rs +++ b/library/std/src/sys/pal/windows/c.rs @@ -20,6 +20,9 @@ pub type WCHAR = u16; pub const INVALID_HANDLE_VALUE: HANDLE = ::core::ptr::without_provenance_mut(-1i32 as _); +pub const FILE_DEVICE_NAMED_PIPE: u32 = 0x00000011; +pub const FILE_DEVICE_CONSOLE: u32 = 0x00000050; + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/exit-success-exit-failure?view=msvc-170 pub const EXIT_SUCCESS: u32 = 0; pub const EXIT_FAILURE: u32 = 1; @@ -188,9 +191,6 @@ pub const FILE_CURRENT: u32 = cu(windows_sys::FILE_CURRENT); pub const FSCTL_GET_REPARSE_POINT: u32 = cu(windows_sys::FSCTL_GET_REPARSE_POINT); pub const FSCTL_SET_REPARSE_POINT: u32 = cu(windows_sys::FSCTL_SET_REPARSE_POINT); -// GetFileType return value (DWORD) -pub const FILE_TYPE_PIPE: u32 = cu(windows_sys::FILE_TYPE_PIPE); - // GetACP return value (UINT) pub const CP_UTF8: u32 = windows_sys::CP_UTF8.cast_unsigned(); @@ -473,6 +473,7 @@ cfg_select! { target_vendor = "uwp" => { windows_link::link_raw_dylib!("ntdll.dll" "system" fn NtCreateFile(filehandle : *mut HANDLE, desiredaccess : ACCESS_MASK, objectattributes : *const OBJECT_ATTRIBUTES, iostatusblock : *mut IO_STATUS_BLOCK, allocationsize : *const i64, fileattributes : u32, shareaccess : u32, createdisposition : u32, createoptions : u32, eabuffer : *const core::ffi::c_void, ealength : u32) -> NTSTATUS); windows_link::link_raw_dylib!("ntdll.dll" "system" fn NtOpenFile(filehandle : *mut HANDLE, desiredaccess : ACCESS_MASK, objectattributes : *const OBJECT_ATTRIBUTES, iostatusblock : *mut IO_STATUS_BLOCK, shareaccess : u32, openoptions : u32) -> NTSTATUS); + windows_link::link_raw_dylib!("ntdll.dll" "system" fn NtQueryVolumeInformationFile(filehandle : HANDLE, iostatusblock : *mut IO_STATUS_BLOCK, fsinformation : *mut core::ffi::c_void, length : u32, fsinformationclass : FS_INFORMATION_CLASS) -> NTSTATUS); windows_link::link_raw_dylib!("ntdll.dll" "system" fn NtReadFile(filehandle : HANDLE, event : HANDLE, apcroutine : PIO_APC_ROUTINE, apccontext : *const core::ffi::c_void, iostatusblock : *mut IO_STATUS_BLOCK, buffer : *mut core::ffi::c_void, length : u32, byteoffset : *const i64, key : *const u32) -> NTSTATUS); windows_link::link_raw_dylib!("ntdll.dll" "system" fn NtWriteFile(filehandle : HANDLE, event : HANDLE, apcroutine : PIO_APC_ROUTINE, apccontext : *const core::ffi::c_void, iostatusblock : *mut IO_STATUS_BLOCK, buffer : *const core::ffi::c_void, length : u32, byteoffset : *const i64, key : *const u32) -> NTSTATUS); windows_link::link_raw_dylib!("ntdll.dll" "system" fn RtlNtStatusToDosError(status : NTSTATUS) -> u32); diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt index 70af36f27907e..71f94954b1356 100644 --- a/library/std/src/sys/pal/windows/c/bindings.txt +++ b/library/std/src/sys/pal/windows/c/bindings.txt @@ -2032,6 +2032,7 @@ FILE_FLAG_RANDOM_ACCESS FILE_FLAG_SEQUENTIAL_SCAN FILE_FLAG_SESSION_AWARE FILE_FLAG_WRITE_THROUGH +FILE_FS_DEVICE_INFORMATION FILE_GENERIC_EXECUTE FILE_GENERIC_READ FILE_GENERIC_WRITE @@ -2084,7 +2085,6 @@ FILE_SYNCHRONOUS_IO_NONALERT FILE_TRAVERSE FILE_TYPE_CHAR FILE_TYPE_DISK -FILE_TYPE_PIPE FILE_TYPE_REMOTE FILE_TYPE_UNKNOWN FILE_WRITE_ATTRIBUTES @@ -2100,6 +2100,7 @@ FileCompressionInfo FileDispositionInfo FileDispositionInfoEx FileEndOfFileInfo +FileFsDeviceInformation FileFullDirectoryInfo FileFullDirectoryRestartInfo FileIdBothDirectoryInfo @@ -2303,6 +2304,7 @@ NO_ERROR NORMAL_PRIORITY_CLASS NtCreateFile NtOpenFile +NtQueryVolumeInformationFile NtReadFile NtSetInformationFile NtWriteFile diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs index 0c865494a6e7c..ae08f790bb40a 100644 --- a/library/std/src/sys/pal/windows/c/windows_sys.rs +++ b/library/std/src/sys/pal/windows/c/windows_sys.rs @@ -78,6 +78,7 @@ windows_link::link!("kernel32.dll" "system" fn MoveFileExW(lpexistingfilename : windows_link::link!("kernel32.dll" "system" fn MultiByteToWideChar(codepage : u32, dwflags : u32, lpmultibytestr : *const i8, cbmultibyte : i32, lpwidecharstr : PWSTR, cchwidechar : i32) -> i32); windows_link::link!("ntdll.dll" "system" fn NtCreateFile(filehandle : *mut HANDLE, desiredaccess : ACCESS_MASK, objectattributes : *const OBJECT_ATTRIBUTES, iostatusblock : *mut IO_STATUS_BLOCK, allocationsize : *const i64, fileattributes : u32, shareaccess : u32, createdisposition : u32, createoptions : u32, eabuffer : *const core::ffi::c_void, ealength : u32) -> NTSTATUS); windows_link::link!("ntdll.dll" "system" fn NtOpenFile(filehandle : *mut HANDLE, desiredaccess : ACCESS_MASK, objectattributes : *const OBJECT_ATTRIBUTES, iostatusblock : *mut IO_STATUS_BLOCK, shareaccess : u32, openoptions : u32) -> NTSTATUS); +windows_link::link!("ntdll.dll" "system" fn NtQueryVolumeInformationFile(filehandle : HANDLE, iostatusblock : *mut IO_STATUS_BLOCK, fsinformation : *mut core::ffi::c_void, length : u32, fsinformationclass : FS_INFORMATION_CLASS) -> NTSTATUS); windows_link::link!("ntdll.dll" "system" fn NtReadFile(filehandle : HANDLE, event : HANDLE, apcroutine : PIO_APC_ROUTINE, apccontext : *const core::ffi::c_void, iostatusblock : *mut IO_STATUS_BLOCK, buffer : *mut core::ffi::c_void, length : u32, byteoffset : *const i64, key : *const u32) -> NTSTATUS); windows_link::link!("ntdll.dll" "system" fn NtSetInformationFile(filehandle : HANDLE, iostatusblock : *mut IO_STATUS_BLOCK, fileinformation : *const core::ffi::c_void, length : u32, fileinformationclass : FILE_INFORMATION_CLASS) -> NTSTATUS); windows_link::link!("ntdll.dll" "system" fn NtWriteFile(filehandle : HANDLE, event : HANDLE, apcroutine : PIO_APC_ROUTINE, apccontext : *const core::ffi::c_void, iostatusblock : *mut IO_STATUS_BLOCK, buffer : *const core::ffi::c_void, length : u32, byteoffset : *const i64, key : *const u32) -> NTSTATUS); @@ -2544,6 +2545,12 @@ pub const FILE_FLAG_RANDOM_ACCESS: i32 = 268435456; pub const FILE_FLAG_SEQUENTIAL_SCAN: i32 = 134217728; pub const FILE_FLAG_SESSION_AWARE: i32 = 8388608; pub const FILE_FLAG_WRITE_THROUGH: u32 = 2147483648; +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct FILE_FS_DEVICE_INFORMATION { + pub DeviceType: u32, + pub Characteristics: u32, +} pub const FILE_GENERIC_EXECUTE: i32 = 1179808; pub const FILE_GENERIC_READ: i32 = 1179785; pub const FILE_GENERIC_WRITE: i32 = 1179926; @@ -2678,7 +2685,6 @@ pub const FILE_SYNCHRONOUS_IO_NONALERT: i32 = 32; pub const FILE_TRAVERSE: i32 = 32; pub const FILE_TYPE_CHAR: i32 = 2; pub const FILE_TYPE_DISK: i32 = 1; -pub const FILE_TYPE_PIPE: i32 = 3; pub const FILE_TYPE_REMOTE: i32 = 32768; pub const FILE_TYPE_UNKNOWN: i32 = 0; pub const FILE_WRITE_ATTRIBUTES: i32 = 256; @@ -2718,6 +2724,7 @@ pub const FORMAT_MESSAGE_IGNORE_INSERTS: i32 = 512; pub const FRS_ERR_SYSVOL_POPULATE_TIMEOUT: i32 = 8014; pub const FSCTL_GET_REPARSE_POINT: i32 = 589992; pub const FSCTL_SET_REPARSE_POINT: i32 = 589988; +pub type FS_INFORMATION_CLASS = i32; pub const FileAlignmentInfo: FILE_INFO_BY_HANDLE_CLASS = 17; pub const FileAllocationInfo: FILE_INFO_BY_HANDLE_CLASS = 5; pub const FileAttributeTagInfo: FILE_INFO_BY_HANDLE_CLASS = 9; @@ -2727,6 +2734,7 @@ pub const FileCompressionInfo: FILE_INFO_BY_HANDLE_CLASS = 8; pub const FileDispositionInfo: FILE_INFO_BY_HANDLE_CLASS = 4; pub const FileDispositionInfoEx: FILE_INFO_BY_HANDLE_CLASS = 21; pub const FileEndOfFileInfo: FILE_INFO_BY_HANDLE_CLASS = 6; +pub const FileFsDeviceInformation: FS_INFORMATION_CLASS = 4; pub const FileFullDirectoryInfo: FILE_INFO_BY_HANDLE_CLASS = 14; pub const FileFullDirectoryRestartInfo: FILE_INFO_BY_HANDLE_CLASS = 15; pub const FileIdBothDirectoryInfo: FILE_INFO_BY_HANDLE_CLASS = 10; diff --git a/library/std/src/sys/stdio/windows.rs b/library/std/src/sys/stdio/windows.rs index 1339048a9c039..82a4424d46989 100644 --- a/library/std/src/sys/stdio/windows.rs +++ b/library/std/src/sys/stdio/windows.rs @@ -77,26 +77,34 @@ pub fn get_handle(handle_id: u32) -> io::Result { } } -fn is_console(handle: c::HANDLE) -> bool { - // `GetConsoleMode` will return false (0) if this is a pipe (we don't care about the reported - // mode). This will only detect Windows Console, not other terminals connected to a pipe like - // MSYS. Which is exactly what we need, as only Windows Console needs a conversion to UTF-16. - let mut mode = 0; - unsafe { c::GetConsoleMode(handle, &mut mode) != 0 } -} - -/// Returns true if the attached console's code page is currently UTF-8. +/// Returns true if `handle` refers to a proper console (aka conhost). #[cfg(not(target_vendor = "win7"))] -fn is_utf8_console() -> bool { - unsafe { c::GetConsoleOutputCP() == c::CP_UTF8 } -} - -#[cfg(target_vendor = "win7")] -fn is_utf8_console() -> bool { - // Windows 7 has a fun "feature" where WriteFile on a console handle will return - // the number of UTF-16 code units written and not the number of bytes from the input string. - // So we always claim the console isn't UTF-8 to trigger the WriteConsole fallback code. - false +fn is_console(handle: c::HANDLE) -> bool { + cfg_select! { + target_vendor = "win7" => { + // `GetConsoleMode` will return false (0) if this is a pipe (we don't care about the reported + // mode). This will only detect Windows Console, not other terminals connected to a pipe like + // MSYS. Which is exactly what we need, as only Windows Console needs a conversion to UTF-16. + let mut mode = 0; + unsafe { c::GetConsoleMode(handle, &mut mode) != 0 } + } + _ => unsafe { + // `GetConsoleMode` incurs a remote RPC round-trip (= bad), so it's best avoided. + // `NtQueryVolumeInformationFile` lets us test the handle directly. + // The required `FILE_DEVICE_CONSOLE` type was introduced in Windows 8. + // Fun fact: `GetFileType` uses `NtQueryVolumeInformationFile` internally. + let mut io_status = MaybeUninit::::uninit(); + let mut device_info = MaybeUninit::::uninit(); + let status = c::NtQueryVolumeInformationFile( + handle, + io_status.as_mut_ptr(), + device_info.as_mut_ptr().cast(), + size_of_val(&device_info) as u32, + c::FileFsDeviceInformation, + ); + status >= 0 && device_info.assume_init_ref().DeviceType == c::FILE_DEVICE_CONSOLE + }, + } } fn write(handle_id: u32, data: &[u8], incomplete_utf8: &mut IncompleteUtf8) -> io::Result { @@ -105,7 +113,12 @@ fn write(handle_id: u32, data: &[u8], incomplete_utf8: &mut IncompleteUtf8) -> i } let handle = get_handle(handle_id)?; - if !is_console(handle) || is_utf8_console() { + + // At the time of writing, console IO on Windows is high latency: + // Windows has no TTY buffers, making every call a fully synchronous RPC. + // So, while always deferring to `write_console_utf16` wastes valuable heap for + // our UTF16 conversion, it handily beats doing 2 (or more) costly RPC round-trips. + if !is_console(handle) { unsafe { let handle = Handle::from_raw_handle(handle); let ret = handle.write(data); diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index 28fe4e86bb7b5..2d31c6809da87 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -1190,6 +1190,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. + "NtQueryVolumeInformationFile" if this.frame_in_std() => { + let [_handle, _iosb, _info, _length, _class] = this.check_shim_sig( + shim_sig!(extern "system" fn(winapi::HANDLE, *_, *_, u32, i32) -> i32), + (link_name, abi, args), + )?; + // Return an error to make std think that nothing is a console. + // This means the normal NtWriteFile is used for stdout/stderr, greatly simplifying our lives. + let not_implemented = this.eval_windows("c", "STATUS_NOT_IMPLEMENTED"); + this.write_scalar(not_implemented, dest)?; + } "GetProcessHeap" if this.frame_in_std() => { let [] = this.check_shim_sig( shim_sig!(extern "system" fn() -> winapi::HANDLE), @@ -1215,24 +1225,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Pretend these does not exist / nothing happened, by returning zero. this.write_null(dest)?; } - "GetConsoleMode" if this.frame_in_std() => { - let [console, mode] = this.check_shim_sig( - shim_sig!(extern "system" fn(winapi::HANDLE, *_) -> winapi::BOOL), - (link_name, abi, args), - )?; - this.read_target_isize(console)?; - this.deref_pointer_as(mode, this.machine.layouts.u32)?; - // Indicate an error. - this.write_null(dest)?; - } - "GetFileType" if this.frame_in_std() => { - let [_file] = this.check_shim_sig( - shim_sig!(extern "system" fn(winapi::HANDLE) -> u32), - (link_name, abi, args), - )?; - // Return unknown file type. - this.write_null(dest)?; - } "AddVectoredExceptionHandler" if this.frame_in_std() => { let [_first, _handler] = this.check_shim_sig( shim_sig!(extern "system" fn(u32, *_) -> *_), From 6c150feb0fded2947740033883c718fe644749b6 Mon Sep 17 00:00:00 2001 From: Robin <1187338299@qq.com> Date: Wed, 14 Oct 2026 09:56:48 +0800 Subject: [PATCH 21/27] fix some nits --- compiler/rustc_hir_analysis/src/check/wfcheck.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 1aa194d7e0013..6401854f5dcfd 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2400,10 +2400,7 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { Err(_guar) => ty::region_constraint::RegionConstraint::new_true(), }; if constraints.is_false() { - self.tcx() - .dcx() - .struct_span_err(span, "top level false constraints are not supported") - .emit(); + self.tcx().dcx().span_err(span, "top level false constraints are not supported"); return; } @@ -2486,7 +2483,7 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { ) .with_spans(forall.span); if constraint.is_false() { - self.tcx().dcx().struct_span_err(forall.span, "forall evaluated to false").emit(); + self.tcx().dcx().span_err(forall.span, "forall evaluated to false"); self.infcx.overwrite_solver_region_constraint( ty::region_constraint::RegionConstraint::new_true(), ); From 9aa0839b0bb6ff81582e7e6059b570e2677bf2af Mon Sep 17 00:00:00 2001 From: khyperia <953151+khyperia@users.noreply.github.com> Date: Wed, 14 Oct 2026 08:36:45 +0200 Subject: [PATCH 22/27] fix issue number for feature(unsized_const_params) --- compiler/rustc_feature/src/unstable.rs | 2 +- library/core/src/marker.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index a35ea37b3bd5f..b8680924c832a 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -768,7 +768,7 @@ declare_features! ( (incomplete, unsafe_fields, "1.85.0", Some(132922)), /// Allows const generic parameters to be defined with types that /// are not `Sized`, e.g. `fn foo() {`. - (incomplete, unsized_const_params, "1.82.0", Some(95174)), + (incomplete, unsized_const_params, "1.82.0", Some(128028)), /// Allows unsized fn parameters. (internal, unsized_fn_params, "1.49.0", Some(48055)), /// Allows using the `#[used(linker)]` (or `#[used(compiler)]`) attribute. diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs index 530af0c3af656..aeabacb0d6364 100644 --- a/library/core/src/marker.rs +++ b/library/core/src/marker.rs @@ -1135,7 +1135,7 @@ marker_impls! { } marker_impls! { - #[unstable(feature = "unsized_const_params", issue = "95174")] + #[unstable(feature = "unsized_const_params", issue = "128028")] #[unstable_feature_bound(unsized_const_params)] ConstParamTy_ for str, From f5e481394242aced873146a2981ad6465036957a Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:36:42 +0200 Subject: [PATCH 23/27] add test --- .../on_unknown/auxiliary/foreign.rs | 12 ++++++++ .../expressions_and_paths_correct_mod_name.rs | 20 +++++++++++++ ...ressions_and_paths_correct_mod_name.stderr | 30 +++++++++++++++++++ ...ions_and_paths_correct_mod_name_foreign.rs | 11 +++++++ ..._and_paths_correct_mod_name_foreign.stderr | 30 +++++++++++++++++++ 5 files changed, 103 insertions(+) create mode 100644 tests/ui/diagnostic_namespace/on_unknown/auxiliary/foreign.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr diff --git a/tests/ui/diagnostic_namespace/on_unknown/auxiliary/foreign.rs b/tests/ui/diagnostic_namespace/on_unknown/auxiliary/foreign.rs new file mode 100644 index 0000000000000..c9269fcb84ca2 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/auxiliary/foreign.rs @@ -0,0 +1,12 @@ +#![feature(diagnostic_on_unknown)] + +mod a { + #[diagnostic::on_unknown( + message = "this={this}, unresolved={unresolved}", + )] + pub mod b {} +} + +pub mod c { + pub use super::a::b as d; +} diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs new file mode 100644 index 0000000000000..92525706e2621 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs @@ -0,0 +1,20 @@ +#![feature(diagnostic_on_unknown)] + +mod a { + #[diagnostic::on_unknown( + message = "this={this}, unresolved={unresolved}", + )] + pub mod b {} +} + +mod c { + pub use super::a::b as d; +} + +use c::d::x; //~ ERROR this=b, unresolved=x + +fn main() { + let _ = c::d::X(); //~ ERROR this=c::d, unresolved=X + + let _: c::d::X; //~ ERROR this=c::d, unresolved=X +} diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr new file mode 100644 index 0000000000000..d2230ee1a8d4d --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr @@ -0,0 +1,30 @@ +error[E0432]: this=b, unresolved=x + --> $DIR/expressions_and_paths_correct_mod_name.rs:14:5 + | +LL | use c::d::x; + | ^^^^^^- + | | + | no `x` in `a::b` + | + = note: unresolved import `c::d::x` + +error[E0425]: this=c::d, unresolved=X + --> $DIR/expressions_and_paths_correct_mod_name.rs:17:19 + | +LL | let _ = c::d::X(); + | ^ not found in `c::d` + | + = note: cannot find function, tuple struct or tuple variant `X` in module `c::d` + +error[E0425]: this=c::d, unresolved=X + --> $DIR/expressions_and_paths_correct_mod_name.rs:19:18 + | +LL | let _: c::d::X; + | ^ not found in `c::d` + | + = note: cannot find type `X` in module `c::d` + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0425, E0432. +For more information about an error, try `rustc --explain E0425`. diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs new file mode 100644 index 0000000000000..abf711b1d41de --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs @@ -0,0 +1,11 @@ +//@ aux-crate:foreign=foreign.rs + +extern crate foreign; + +use foreign::c::d::x; //~ ERROR this=b, unresolved=x + +fn main() { + let _ = foreign::c::d::X(); //~ ERROR this=foreign::c::d, unresolved=X + + let _: foreign::c::d::X; //~ ERROR this=foreign::c::d, unresolved=X +} diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr new file mode 100644 index 0000000000000..3b95a3e2d0ec1 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr @@ -0,0 +1,30 @@ +error[E0432]: this=b, unresolved=x + --> $DIR/expressions_and_paths_correct_mod_name_foreign.rs:5:5 + | +LL | use foreign::c::d::x; + | ^^^^^^^^^^^^^^^- + | | + | no `x` in `a::b` + | + = note: unresolved import `foreign::c::d::x` + +error[E0425]: this=foreign::c::d, unresolved=X + --> $DIR/expressions_and_paths_correct_mod_name_foreign.rs:8:28 + | +LL | let _ = foreign::c::d::X(); + | ^ not found in `foreign::c::d` + | + = note: cannot find function, tuple struct or tuple variant `X` in module `foreign::c::d` + +error[E0425]: this=foreign::c::d, unresolved=X + --> $DIR/expressions_and_paths_correct_mod_name_foreign.rs:10:27 + | +LL | let _: foreign::c::d::X; + | ^ not found in `foreign::c::d` + | + = note: cannot find type `X` in module `foreign::c::d` + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0425, E0432. +For more information about an error, try `rustc --explain E0425`. From f76d6135a1dd54c50774c737cb94b12f7db08f2a Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Wed, 14 Oct 2026 09:31:41 +0200 Subject: [PATCH 24/27] Use proper mod name for on_unknown attribute --- .../rustc_resolve/src/diagnostics/impls.rs | 32 ++++++++++--------- .../rustc_resolve/src/late/diagnostics.rs | 5 +-- .../expressions_and_paths_correct_mod_name.rs | 4 +-- ...ressions_and_paths_correct_mod_name.stderr | 4 +-- ...ions_and_paths_correct_mod_name_foreign.rs | 4 +-- ..._and_paths_correct_mod_name_foreign.stderr | 4 +-- 6 files changed, 28 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index f1400c83e4295..18a02666cfc4a 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -206,21 +206,18 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .iter() .map(|(import, import_error)| { if let Some(ModuleOrUniformRoot::Module(module_data)) = import.imported_module.get() - && let ModuleKind::Def(DefKind::Mod, def_id, _, name) = module_data.kind + && let ModuleKind::Def(DefKind::Mod, def_id, ..) = module_data.kind { - let Some(directive) = self.on_unknown_data(def_id) else { + let Some((this, directive)) = self.on_unknown_data(def_id) else { return CustomDiagnostic::default(); }; - let this = if let Some(name) = name { - name.to_string() - } else if let Some(crate_name) = &self.tcx.sess.opts.crate_name { - crate_name.to_string() - } else { - "".to_string() - }; let unresolved = import_error.segment.map(|s| s.name).unwrap_or(kw::Underscore); - let args = FormatArgs { this, unresolved: unresolved.to_string(), .. }; + let args = FormatArgs { + this: this.to_string(), + unresolved: unresolved.to_string(), + .. + }; directive.eval(None, &args) } else { @@ -3867,12 +3864,17 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } - /// Gets the `#[diagnostic::on_unknown]` attribute data associated with this `DefId`. - pub(crate) fn on_unknown_data(&self, def_id: DefId) -> Option<&Directive> { - match def_id.as_local() { + /// Gets the name and `#[diagnostic::on_unknown]` attribute data associated with this `DefId`. + pub(crate) fn on_unknown_data(&self, def_id: DefId) -> Option<(Symbol, &Directive)> { + let directive = match def_id.as_local() { Some(local) => Some(self.on_unknown_data.get(&local)?.directive.as_ref()), - None => find_attr!(self.tcx, def_id, OnUnknown{ directive } => directive)?.as_deref(), - } + None => find_attr!(self.tcx, def_id, OnUnknown { directive } => directive)?.as_deref(), + }?; + + let name = + self.expect_module(def_id).name().unwrap_or_else(|| self.tcx.crate_name(def_id.krate)); + + Some((name, directive)) } } diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 821c16ceee3e5..83e5fbef9bcb9 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -558,9 +558,10 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { }; let mut notes = Vec::new(); if let Some(module_def_id) = module - && let Some(directive) = self.r.on_unknown_data(module_def_id) + && let Some((this, directive)) = self.r.on_unknown_data(module_def_id) { - let args = FormatArgs { unresolved: item_ident.to_string(), this: mod_str, .. }; + let args = + FormatArgs { unresolved: item_ident.to_string(), this: this.to_string(), .. }; let CustomDiagnostic { message, label, diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs index 92525706e2621..d8bf14ccc54c3 100644 --- a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.rs @@ -14,7 +14,7 @@ mod c { use c::d::x; //~ ERROR this=b, unresolved=x fn main() { - let _ = c::d::X(); //~ ERROR this=c::d, unresolved=X + let _ = c::d::X(); //~ ERROR this=b, unresolved=X - let _: c::d::X; //~ ERROR this=c::d, unresolved=X + let _: c::d::X; //~ ERROR this=b, unresolved=X } diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr index d2230ee1a8d4d..a269acdf42b17 100644 --- a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name.stderr @@ -8,7 +8,7 @@ LL | use c::d::x; | = note: unresolved import `c::d::x` -error[E0425]: this=c::d, unresolved=X +error[E0425]: this=b, unresolved=X --> $DIR/expressions_and_paths_correct_mod_name.rs:17:19 | LL | let _ = c::d::X(); @@ -16,7 +16,7 @@ LL | let _ = c::d::X(); | = note: cannot find function, tuple struct or tuple variant `X` in module `c::d` -error[E0425]: this=c::d, unresolved=X +error[E0425]: this=b, unresolved=X --> $DIR/expressions_and_paths_correct_mod_name.rs:19:18 | LL | let _: c::d::X; diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs index abf711b1d41de..6cb8872984b36 100644 --- a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.rs @@ -5,7 +5,7 @@ extern crate foreign; use foreign::c::d::x; //~ ERROR this=b, unresolved=x fn main() { - let _ = foreign::c::d::X(); //~ ERROR this=foreign::c::d, unresolved=X + let _ = foreign::c::d::X(); //~ ERROR this=b, unresolved=X - let _: foreign::c::d::X; //~ ERROR this=foreign::c::d, unresolved=X + let _: foreign::c::d::X; //~ ERROR this=b, unresolved=X } diff --git a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr index 3b95a3e2d0ec1..ee1765828718f 100644 --- a/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr +++ b/tests/ui/diagnostic_namespace/on_unknown/expressions_and_paths_correct_mod_name_foreign.stderr @@ -8,7 +8,7 @@ LL | use foreign::c::d::x; | = note: unresolved import `foreign::c::d::x` -error[E0425]: this=foreign::c::d, unresolved=X +error[E0425]: this=b, unresolved=X --> $DIR/expressions_and_paths_correct_mod_name_foreign.rs:8:28 | LL | let _ = foreign::c::d::X(); @@ -16,7 +16,7 @@ LL | let _ = foreign::c::d::X(); | = note: cannot find function, tuple struct or tuple variant `X` in module `foreign::c::d` -error[E0425]: this=foreign::c::d, unresolved=X +error[E0425]: this=b, unresolved=X --> $DIR/expressions_and_paths_correct_mod_name_foreign.rs:10:27 | LL | let _: foreign::c::d::X; From 731b6596f35d8d05d42e2b2f5b4cf01749884e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 12 Oct 2026 15:41:10 +0200 Subject: [PATCH 25/27] bless tests --- ...const-argument-cross-crate-mismatch.stderr | 33 +++++++++-------- .../issue-62504.full.stderr | 36 +++++++++++++++---- .../issue-62504.min.stderr | 35 ++++++++++++------ .../generic_const_exprs/issue-62504.rs | 6 +++- .../const-generics/ice-unevaluated.rs} | 2 +- tests/ui/const-generics/transmute_no_gate.rs | 2 ++ .../const-generics/transmute_no_gate.stderr | 33 +++++++++++++++-- 7 files changed, 108 insertions(+), 39 deletions(-) rename tests/{crashes/118603.rs => ui/const-generics/ice-unevaluated.rs} (97%) diff --git a/tests/ui/const-generics/const-argument-cross-crate-mismatch.stderr b/tests/ui/const-generics/const-argument-cross-crate-mismatch.stderr index f58821283e19f..1e33dd31a4129 100644 --- a/tests/ui/const-generics/const-argument-cross-crate-mismatch.stderr +++ b/tests/ui/const-generics/const-argument-cross-crate-mismatch.stderr @@ -1,30 +1,29 @@ error[E0308]: mismatched types - --> $DIR/const-argument-cross-crate-mismatch.rs:6:67 + --> $DIR/const-argument-cross-crate-mismatch.rs:6:41 | LL | let _ = const_generic_lib::function(const_generic_lib::Struct([0u8, 1u8])); - | ------------------------- ^^^^^^^^^^ expected an array with a size of 3, found one with a size of 2 - | | - | arguments to this struct are incorrect + | --------------------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `3`, found `2` + | | + | arguments to this function are incorrect | -note: tuple struct defined here - --> $DIR/auxiliary/const_generic_lib.rs:1:12 + = note: expected struct `Struct<3>` + found struct `Struct<2>` +note: function defined here + --> $DIR/auxiliary/const_generic_lib.rs:5:8 | -LL | pub struct Struct(pub [u8; N]); - | ^^^^^^ +LL | pub fn function(value: Struct<3>) -> u8 { + | ^^^^^^^^ error[E0308]: mismatched types - --> $DIR/const-argument-cross-crate-mismatch.rs:8:65 + --> $DIR/const-argument-cross-crate-mismatch.rs:8:39 | LL | let _: const_generic_lib::Alias = const_generic_lib::Struct([0u8, 1u8, 2u8]); - | ------------------------- ^^^^^^^^^^^^^^^ expected an array with a size of 2, found one with a size of 3 - | | - | arguments to this struct are incorrect + | ------------------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `2`, found `3` + | | + | expected due to this | -note: tuple struct defined here - --> $DIR/auxiliary/const_generic_lib.rs:1:12 - | -LL | pub struct Struct(pub [u8; N]); - | ^^^^^^ + = note: expected struct `Struct<2>` + found struct `Struct<3>` error: aborting due to 2 previous errors diff --git a/tests/ui/const-generics/generic_const_exprs/issue-62504.full.stderr b/tests/ui/const-generics/generic_const_exprs/issue-62504.full.stderr index b64c1c17df601..cdcfe80093f96 100644 --- a/tests/ui/const-generics/generic_const_exprs/issue-62504.full.stderr +++ b/tests/ui/const-generics/generic_const_exprs/issue-62504.full.stderr @@ -1,17 +1,39 @@ +error: unconstrained generic constant + --> $DIR/issue-62504.rs:18:25 + | +LL | ArrayHolder([0; Self::SIZE]) + | ^^^^^^^^^^ + | +help: try adding a `where` bound + | +LL | pub const fn new() -> Self where [(); Self::SIZE]: { + | +++++++++++++++++++++++ + +error: unconstrained generic constant + --> $DIR/issue-62504.rs:18:9 + | +LL | ArrayHolder([0; Self::SIZE]) + | ^^^^^^^^^^^ + | +help: try adding a `where` bound + | +LL | pub const fn new() -> Self where [(); Self::SIZE]: { + | +++++++++++++++++++++++ + error[E0308]: mismatched types - --> $DIR/issue-62504.rs:18:21 + --> $DIR/issue-62504.rs:18:9 | LL | ArrayHolder([0; Self::SIZE]) - | ^^^^^^^^^^^^^^^ expected `X`, found `Self::SIZE` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `X`, found `Self::SIZE` | = note: expected constant `X` found constant `Self::SIZE` error: unconstrained generic constant - --> $DIR/issue-62504.rs:18:25 + --> $DIR/issue-62504.rs:18:9 | LL | ArrayHolder([0; Self::SIZE]) - | ^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: try adding a `where` bound | @@ -19,7 +41,7 @@ LL | pub const fn new() -> Self where [(); Self::SIZE]: { | +++++++++++++++++++++++ error[E0284]: type annotations needed for `ArrayHolder<_>` - --> $DIR/issue-62504.rs:26:9 + --> $DIR/issue-62504.rs:30:9 | LL | let mut array = ArrayHolder::new(); | ^^^^^^^^^ ----------- type must be known at this point @@ -35,7 +57,7 @@ LL | let mut array: ArrayHolder = ArrayHolder::new(); | ++++++++++++++++ error[E0284]: type annotations needed for `ArrayHolder<_>` - --> $DIR/issue-62504.rs:26:9 + --> $DIR/issue-62504.rs:30:9 | LL | let mut array = ArrayHolder::new(); | ^^^^^^^^^ ------------------ type must be known at this point @@ -52,7 +74,7 @@ help: consider giving `array` an explicit type, where the value of const paramet LL | let mut array: ArrayHolder = ArrayHolder::new(); | ++++++++++++++++ -error: aborting due to 4 previous errors +error: aborting due to 6 previous errors Some errors have detailed explanations: E0284, E0308. For more information about an error, try `rustc --explain E0284`. diff --git a/tests/ui/const-generics/generic_const_exprs/issue-62504.min.stderr b/tests/ui/const-generics/generic_const_exprs/issue-62504.min.stderr index ef4655c8dc21f..8b8e4f87d7381 100644 --- a/tests/ui/const-generics/generic_const_exprs/issue-62504.min.stderr +++ b/tests/ui/const-generics/generic_const_exprs/issue-62504.min.stderr @@ -6,22 +6,35 @@ LL | ArrayHolder([0; Self::SIZE]) | = note: this may fail depending on what value the parameter takes +error: constant expression depends on a generic parameter + --> $DIR/issue-62504.rs:18:9 + | +LL | ArrayHolder([0; Self::SIZE]) + | ^^^^^^^^^^^ + | + = note: this may fail depending on what value the parameter takes + error[E0308]: mismatched types - --> $DIR/issue-62504.rs:18:21 + --> $DIR/issue-62504.rs:18:9 | +LL | pub const fn new() -> Self { + | ---- expected `ArrayHolder` because of return type LL | ArrayHolder([0; Self::SIZE]) - | ----------- ^^^^^^^^^^^^^^^ expected an array with a size of X, found one with a size of Self::SIZE - | | - | arguments to this struct are incorrect + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `X`, found `Self::SIZE` | -note: tuple struct defined here - --> $DIR/issue-62504.rs:14:8 + = note: expected struct `ArrayHolder` + found struct `ArrayHolder` + +error: constant expression depends on a generic parameter + --> $DIR/issue-62504.rs:18:9 | -LL | struct ArrayHolder([u32; X]); - | ^^^^^^^^^^^ +LL | ArrayHolder([0; Self::SIZE]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this may fail depending on what value the parameter takes error[E0284]: type annotations needed for `ArrayHolder<_>` - --> $DIR/issue-62504.rs:26:9 + --> $DIR/issue-62504.rs:30:9 | LL | let mut array = ArrayHolder::new(); | ^^^^^^^^^ ----------- type must be known at this point @@ -37,7 +50,7 @@ LL | let mut array: ArrayHolder = ArrayHolder::new(); | ++++++++++++++++ error[E0284]: type annotations needed for `ArrayHolder<_>` - --> $DIR/issue-62504.rs:26:9 + --> $DIR/issue-62504.rs:30:9 | LL | let mut array = ArrayHolder::new(); | ^^^^^^^^^ ------------------ type must be known at this point @@ -54,7 +67,7 @@ help: consider giving `array` an explicit type, where the value of const paramet LL | let mut array: ArrayHolder = ArrayHolder::new(); | ++++++++++++++++ -error: aborting due to 4 previous errors +error: aborting due to 6 previous errors Some errors have detailed explanations: E0284, E0308. For more information about an error, try `rustc --explain E0284`. diff --git a/tests/ui/const-generics/generic_const_exprs/issue-62504.rs b/tests/ui/const-generics/generic_const_exprs/issue-62504.rs index f021e1036143b..830838905d378 100644 --- a/tests/ui/const-generics/generic_const_exprs/issue-62504.rs +++ b/tests/ui/const-generics/generic_const_exprs/issue-62504.rs @@ -18,7 +18,11 @@ impl ArrayHolder { ArrayHolder([0; Self::SIZE]) //~^ ERROR mismatched types //[full]~^^ ERROR unconstrained generic constant - //[min]~^^^ ERROR constant expression depends on a generic parameter + //[full]~^^^ ERROR unconstrained generic constant + //[full]~^^^^ ERROR unconstrained generic constant + //[min]~^^^^^ ERROR constant expression depends on a generic parameter + //[min]~| ERROR constant expression depends on a generic parameter + //[min]~| ERROR constant expression depends on a generic parameter } } diff --git a/tests/crashes/118603.rs b/tests/ui/const-generics/ice-unevaluated.rs similarity index 97% rename from tests/crashes/118603.rs rename to tests/ui/const-generics/ice-unevaluated.rs index 1a3e3bed59d40..d03b3c34ca5ee 100644 --- a/tests/crashes/118603.rs +++ b/tests/ui/const-generics/ice-unevaluated.rs @@ -1,5 +1,5 @@ -//@ known-bug: #118603 //@ compile-flags: -Copt-level=0 +//@ check-pass // ignore-tidy-file-linelength #![feature(generic_const_exprs)] diff --git a/tests/ui/const-generics/transmute_no_gate.rs b/tests/ui/const-generics/transmute_no_gate.rs index e1ac44390a5f8..72a1358480f81 100644 --- a/tests/ui/const-generics/transmute_no_gate.rs +++ b/tests/ui/const-generics/transmute_no_gate.rs @@ -83,9 +83,11 @@ fn main() { let _ = ident([[0; 8]; 16]); let _ = flatten([[0; 13]; 5]); let _: [[_; 5]; 13] = coagulate([0; 65]); + //~^ ERROR mismatched types let _ = flatten_3d([[[0; 3]; 13]; 5]); let _ = flatten_somewhat([[[0; 3]; 13]; 5]); let _ = known_size([16; 13]); let _: [u16; 5] = condense_bytes([16u8; 10]); + //~^ ERROR mismatched types let _ = singleton_each([16; 10]); } diff --git a/tests/ui/const-generics/transmute_no_gate.stderr b/tests/ui/const-generics/transmute_no_gate.stderr index 9c271b34849c0..01b53dc301cfb 100644 --- a/tests/ui/const-generics/transmute_no_gate.stderr +++ b/tests/ui/const-generics/transmute_no_gate.stderr @@ -1,3 +1,31 @@ +error[E0308]: mismatched types + --> $DIR/transmute_no_gate.rs:85:35 + | +LL | let _: [[_; 5]; 13] = coagulate([0; 65]); + | --------- ^^^^^^^ expected an array with a size of _ * _, found one with a size of 65 + | | + | arguments to this function are incorrect + | +note: function defined here + --> $DIR/transmute_no_gate.rs:25:4 + | +LL | fn coagulate(v: [u32; H*W]) -> [[u32; W];H] { + | ^^^^^^^^^ ------------- + +error[E0308]: mismatched types + --> $DIR/transmute_no_gate.rs:90:36 + | +LL | let _: [u16; 5] = condense_bytes([16u8; 10]); + | -------------- ^^^^^^^^^^ expected an array with a size of _ * 2, found one with a size of 10 + | | + | arguments to this function are incorrect + | +note: function defined here + --> $DIR/transmute_no_gate.rs:57:4 + | +LL | fn condense_bytes(v: [u8; L * 2]) -> [u16; L] { + | ^^^^^^^^^^^^^^ -------------- + error[E0512]: cannot transmute between types of different sizes, or dependently-sized types --> $DIR/transmute_no_gate.rs:7:5 | @@ -79,6 +107,7 @@ LL | std::mem::transmute(v) = note: source type: `[[u32; 2 * H]; W + W]` (this type does not have a fixed size) = note: target type: `[[u32; W + W]; 2 * H]` (this type does not have a fixed size) -error: aborting due to 9 previous errors +error: aborting due to 11 previous errors -For more information about this error, try `rustc --explain E0512`. +Some errors have detailed explanations: E0308, E0512. +For more information about an error, try `rustc --explain E0308`. From 3df075cdaa40728e9c552bd99755271eb259ffed Mon Sep 17 00:00:00 2001 From: albab-hasan Date: Wed, 14 Oct 2026 16:32:35 +0600 Subject: [PATCH 26/27] point at the opaque when its hidden type cannot be inferred the note for an uninferable hidden type now points at the opaque definition, unless the error already points inside it --- compiler/rustc_hir_typeck/src/opaque_types.rs | 31 +++++++++++-------- ...mbig-hr-projection-issue-93340.next.stderr | 6 +++- ...-method-resolution-opaque-type.next.stderr | 6 +++- .../auto-trait-selection-freeze.next.stderr | 6 +++- .../auto-trait-selection.next.stderr | 6 +++- .../recursive-in-exhaustiveness.next.stderr | 12 +++++-- .../two_tait_defining_each_other2.next.stderr | 6 +++- ...e_of-tait-in-defining-scope.is_send.stderr | 6 +++- ..._of-tait-in-defining-scope.not_send.stderr | 6 +++- ...ursive-hidden-type-canonicalization.stderr | 6 +++- .../constrain_in_projection2.next.stderr | 6 +++- .../issue-84660-unsoundness.next.stderr | 6 +++- ...ution_trait_method_from_opaque.next.stderr | 6 +++- .../nested-tait-inference2.next.stderr | 6 +++- 14 files changed, 88 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/opaque_types.rs b/compiler/rustc_hir_typeck/src/opaque_types.rs index c96144d849944..da9162629ec80 100644 --- a/compiler/rustc_hir_typeck/src/opaque_types.rs +++ b/compiler/rustc_hir_typeck/src/opaque_types.rs @@ -190,19 +190,24 @@ impl<'tcx> FnCtxt<'_, 'tcx> { .filter_map(ty::GenericArg::as_term) .find(|term| term.is_infer()) .unwrap_or_else(|| hidden_type.ty.into()); - self.err_ctxt() - .emit_inference_failure_err( - self.body_def_id, - hidden_type.span, - infer_var, - TypeAnnotationNeeded::E0282, - false, - ) - // The shared E0282 label only says "cannot infer type", which gives no - // hint that the ambiguity is in an opaque's hidden type rather than an - // ordinary local inference failure. - .with_note("cannot infer type of hidden type of opaque") - .emit_err() + let mut err = self.err_ctxt().emit_inference_failure_err( + self.body_def_id, + hidden_type.span, + infer_var, + TypeAnnotationNeeded::E0282, + false, + ); + // The shared E0282 label only says "cannot infer type", which gives no + // hint that the ambiguity is in an opaque's hidden type rather than an + // ordinary local inference failure. + let msg = "cannot infer type of hidden type of opaque"; + let opaque_span = self.tcx.def_span(def_id); + if err.span.primary_span().is_some_and(|sp| opaque_span.contains(sp)) { + err.note(msg); + } else { + err.span_note(opaque_span, msg); + } + err.emit_err() } } }; diff --git a/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.next.stderr b/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.next.stderr index d2ed88c1125ba..d7be74ee825cd 100644 --- a/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.next.stderr +++ b/tests/ui/generic-associated-types/ambig-hr-projection-issue-93340.next.stderr @@ -4,7 +4,11 @@ error[E0282]: type annotations needed LL | cmp_eq | ^^^^^^ cannot infer type of the type parameter `A` declared on the function `cmp_eq` | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/ambig-hr-projection-issue-93340.rs:15:6 + | +LL | ) -> impl Fn(A::RefType<'_>, B::RefType<'_>) -> O { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider specifying the generic arguments | LL | cmp_eq:: diff --git a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/norm-before-method-resolution-opaque-type.next.stderr b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/norm-before-method-resolution-opaque-type.next.stderr index cd1f2aa14acee..24c412caf2491 100644 --- a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/norm-before-method-resolution-opaque-type.next.stderr +++ b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/norm-before-method-resolution-opaque-type.next.stderr @@ -4,7 +4,11 @@ error[E0282]: type annotations needed for `>::Out<_>` LL | let x = *x; | ^ | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/norm-before-method-resolution-opaque-type.rs:18:12 + | +LL | type Foo = impl Sized; + | ^^^^^^^^^^ help: consider giving `x` an explicit type, where the placeholders `_` are specified | LL | let x: <_ as Trait<'static>>::Out<_> = *x; diff --git a/tests/ui/impl-trait/auto-trait-selection-freeze.next.stderr b/tests/ui/impl-trait/auto-trait-selection-freeze.next.stderr index 4f08d635cabdc..88ff9bd320111 100644 --- a/tests/ui/impl-trait/auto-trait-selection-freeze.next.stderr +++ b/tests/ui/impl-trait/auto-trait-selection-freeze.next.stderr @@ -4,7 +4,11 @@ error[E0282]: type annotations needed LL | if false { is_trait(foo()) } else { Default::default() } | ^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `is_trait` | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/auto-trait-selection-freeze.rs:19:13 + | +LL | fn foo() -> impl Sized { + | ^^^^^^^^^^ help: consider specifying the generic arguments | LL | if false { is_trait::(foo()) } else { Default::default() } diff --git a/tests/ui/impl-trait/auto-trait-selection.next.stderr b/tests/ui/impl-trait/auto-trait-selection.next.stderr index dbad5680781ec..daf2f9a4617df 100644 --- a/tests/ui/impl-trait/auto-trait-selection.next.stderr +++ b/tests/ui/impl-trait/auto-trait-selection.next.stderr @@ -4,7 +4,11 @@ error[E0282]: type annotations needed LL | if false { is_trait(foo()) } else { Default::default() } | ^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `is_trait` | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/auto-trait-selection.rs:15:13 + | +LL | fn foo() -> impl Sized { + | ^^^^^^^^^^ help: consider specifying the generic arguments | LL | if false { is_trait::(foo()) } else { Default::default() } diff --git a/tests/ui/impl-trait/recursive-in-exhaustiveness.next.stderr b/tests/ui/impl-trait/recursive-in-exhaustiveness.next.stderr index 878e10557f26a..63e53bd75bdb2 100644 --- a/tests/ui/impl-trait/recursive-in-exhaustiveness.next.stderr +++ b/tests/ui/impl-trait/recursive-in-exhaustiveness.next.stderr @@ -4,7 +4,11 @@ error[E0282]: type annotations needed for `(_,)` LL | let (x,) = (build(x),); | ^^^^ | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/recursive-in-exhaustiveness.rs:17:22 + | +LL | fn build(x: T) -> impl Sized { + | ^^^^^^^^^^ help: consider giving this pattern a type, where the placeholder `_` is specified | LL | let (x,): (_,) = (build(x),); @@ -16,7 +20,11 @@ error[E0282]: type annotations needed for `((_,),)` LL | let (x,) = (build2(x),); | ^^^^ | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/recursive-in-exhaustiveness.rs:27:23 + | +LL | fn build2(x: T) -> impl Sized { + | ^^^^^^^^^^ help: consider giving this pattern a type, where the placeholder `_` is specified | LL | let (x,): ((_,),) = (build2(x),); diff --git a/tests/ui/impl-trait/two_tait_defining_each_other2.next.stderr b/tests/ui/impl-trait/two_tait_defining_each_other2.next.stderr index a2d25fafb2a0b..488ad507f041b 100644 --- a/tests/ui/impl-trait/two_tait_defining_each_other2.next.stderr +++ b/tests/ui/impl-trait/two_tait_defining_each_other2.next.stderr @@ -4,7 +4,11 @@ error[E0282]: type annotations needed LL | fn muh(x: A) -> B { | ^ cannot infer type | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/two_tait_defining_each_other2.rs:6:10 + | +LL | type A = impl Foo; + | ^^^^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/traits/next-solver/opaques/dont-type_of-tait-in-defining-scope.is_send.stderr b/tests/ui/traits/next-solver/opaques/dont-type_of-tait-in-defining-scope.is_send.stderr index fc285abc9fe40..a2d31d4d51ff6 100644 --- a/tests/ui/traits/next-solver/opaques/dont-type_of-tait-in-defining-scope.is_send.stderr +++ b/tests/ui/traits/next-solver/opaques/dont-type_of-tait-in-defining-scope.is_send.stderr @@ -4,7 +4,11 @@ error[E0282]: type annotations needed LL | needs_send::(); | ^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `needs_send` | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/dont-type_of-tait-in-defining-scope.rs:7:12 + | +LL | type Foo = impl Send; + | ^^^^^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/traits/next-solver/opaques/dont-type_of-tait-in-defining-scope.not_send.stderr b/tests/ui/traits/next-solver/opaques/dont-type_of-tait-in-defining-scope.not_send.stderr index fc285abc9fe40..5af23f405e054 100644 --- a/tests/ui/traits/next-solver/opaques/dont-type_of-tait-in-defining-scope.not_send.stderr +++ b/tests/ui/traits/next-solver/opaques/dont-type_of-tait-in-defining-scope.not_send.stderr @@ -4,7 +4,11 @@ error[E0282]: type annotations needed LL | needs_send::(); | ^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `needs_send` | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/dont-type_of-tait-in-defining-scope.rs:10:12 + | +LL | type Foo = impl Sized; + | ^^^^^^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/traits/next-solver/opaques/recursive-hidden-type-canonicalization.stderr b/tests/ui/traits/next-solver/opaques/recursive-hidden-type-canonicalization.stderr index d6fc9584ef516..3e931b93f5dbf 100644 --- a/tests/ui/traits/next-solver/opaques/recursive-hidden-type-canonicalization.stderr +++ b/tests/ui/traits/next-solver/opaques/recursive-hidden-type-canonicalization.stderr @@ -4,7 +4,11 @@ error[E0282]: type annotations needed for `*mut _` LL | let r = random_paulis().unwrap(); | ^ | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/recursive-hidden-type-canonicalization.rs:18:35 + | +LL | fn random_paulis() -> Option<*mut impl Sized> { + | ^^^^^^^^^^ help: consider giving `r` an explicit type, where the placeholder `_` is specified | LL | let r: *mut _ = random_paulis().unwrap(); diff --git a/tests/ui/type-alias-impl-trait/constrain_in_projection2.next.stderr b/tests/ui/type-alias-impl-trait/constrain_in_projection2.next.stderr index 3862479fee380..d91b4490abe88 100644 --- a/tests/ui/type-alias-impl-trait/constrain_in_projection2.next.stderr +++ b/tests/ui/type-alias-impl-trait/constrain_in_projection2.next.stderr @@ -4,7 +4,11 @@ error[E0282]: type annotations needed LL | let x = >::Assoc::default(); | ^^^ cannot infer type | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/constrain_in_projection2.rs:12:12 + | +LL | type Bar = impl Sized; + | ^^^^^^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/type-alias-impl-trait/issue-84660-unsoundness.next.stderr b/tests/ui/type-alias-impl-trait/issue-84660-unsoundness.next.stderr index d025a821d1b45..2836a83ea76c9 100644 --- a/tests/ui/type-alias-impl-trait/issue-84660-unsoundness.next.stderr +++ b/tests/ui/type-alias-impl-trait/issue-84660-unsoundness.next.stderr @@ -13,7 +13,11 @@ error[E0282]: type annotations needed LL | fn convert(_i: In) -> Self::Out { | ^^^^^^^^^ cannot infer type | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/issue-84660-unsoundness.rs:12:12 + | +LL | type Bar = impl Foo; + | ^^^^^^^^ error: aborting due to 2 previous errors diff --git a/tests/ui/type-alias-impl-trait/method_resolution_trait_method_from_opaque.next.stderr b/tests/ui/type-alias-impl-trait/method_resolution_trait_method_from_opaque.next.stderr index a2e5b3cc972ef..81b929b370470 100644 --- a/tests/ui/type-alias-impl-trait/method_resolution_trait_method_from_opaque.next.stderr +++ b/tests/ui/type-alias-impl-trait/method_resolution_trait_method_from_opaque.next.stderr @@ -4,7 +4,11 @@ error[E0282]: type annotations needed LL | self.bar.next().unwrap(); | ^^^^ | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/method_resolution_trait_method_from_opaque.rs:18:13 + | +LL | type Tait = impl Iterator; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try using a fully qualified path to specify the expected types | LL - self.bar.next().unwrap(); diff --git a/tests/ui/type-alias-impl-trait/nested-tait-inference2.next.stderr b/tests/ui/type-alias-impl-trait/nested-tait-inference2.next.stderr index 9e8857201e72c..fac807b5bc3dc 100644 --- a/tests/ui/type-alias-impl-trait/nested-tait-inference2.next.stderr +++ b/tests/ui/type-alias-impl-trait/nested-tait-inference2.next.stderr @@ -4,7 +4,11 @@ error[E0282]: type annotations needed LL | fn foo() -> impl Foo { | ^^^^^^^^^^^^^^ cannot infer type | - = note: cannot infer type of hidden type of opaque +note: cannot infer type of hidden type of opaque + --> $DIR/nested-tait-inference2.rs:10:13 + | +LL | type FooX = impl Debug; + | ^^^^^^^^^^ error: aborting due to 1 previous error From 75b48eeb0f8ea180fb31e534a289c89fb0a0aa98 Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Wed, 14 Oct 2026 14:31:49 +0000 Subject: [PATCH 27/27] Implement better resolution for delegation mapping field --- .../rustc_ast_lowering/src/delegation/mod.rs | 4 +- compiler/rustc_hir_typeck/src/expr.rs | 89 ++++++++----- compiler/rustc_privacy/src/lib.rs | 11 +- .../pretty/delegation/self-mapping-output.pp | 4 +- .../delegation/self-mapping-output-privacy.rs | 14 ++ .../self-mapping-output-privacy.stderr | 18 ++- tests/ui/delegation/self-mapping-output.rs | 6 +- .../ui/delegation/self-mapping-output.stderr | 124 +++++++----------- .../self-type-mapping-field-name.rs | 56 ++++++++ 9 files changed, 207 insertions(+), 119 deletions(-) create mode 100644 tests/ui/delegation/self-type-mapping-field-name.rs diff --git a/compiler/rustc_ast_lowering/src/delegation/mod.rs b/compiler/rustc_ast_lowering/src/delegation/mod.rs index b2c0e1ffa81b1..eb980d31ce1ef 100644 --- a/compiler/rustc_ast_lowering/src/delegation/mod.rs +++ b/compiler/rustc_ast_lowering/src/delegation/mod.rs @@ -51,7 +51,7 @@ use rustc_hir::{self as hir, FnDeclFlags, QPath}; use rustc_middle::ty::Asyncness; use rustc_span::def_id::DefId; use rustc_span::symbol::kw; -use rustc_span::{Ident, Span, Symbol, sym}; +use rustc_span::{Ident, Span, Symbol}; use crate::delegation::generics::{GenericsGenerationResults, GenericsPosition}; use crate::delegation::resolution::resolver::DelegationResolver; @@ -480,7 +480,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.arena.alloc_slice(&[hir::ExprField { hir_id: self.next_id(), is_shorthand: false, - ident: Ident::new(sym::integer(0), span), + ident: Ident::new(kw::Underscore, span), expr: self.arena.alloc(call), span, }]), diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index b1b8ecb7d1844..6a85430248d84 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1898,10 +1898,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let adt_kind = adt.adt_kind(); + let get_field_ident = |field: &ty::FieldDef| field.ident(tcx).normalize_to_macros_2_0(); let mut remaining_fields = variant .fields .iter_enumerated() - .map(|(i, field)| (field.ident(tcx).normalize_to_macros_2_0(), (i, field))) + .map(|(i, field)| (get_field_ident(field), (i, field))) .collect::>(); let mut seen_fields = FxHashMap::default(); @@ -1919,41 +1920,65 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Type-check each field. for (idx, field) in hir_fields.iter().enumerate() { - let ident = tcx.adjust_ident(field.ident, variant.def_id); - let field_type = if let Some((i, v_field)) = remaining_fields.remove(&ident) { - seen_fields.insert(ident, field.span); - self.write_field_index(field.hir_id, i); - - // We don't look at stability attributes on - // struct-like enums (yet...), but it's definitely not - // a bug to have constructed one. - if adt_kind != AdtKind::Enum { - tcx.check_stability(v_field.did, Some(field.hir_id), field.span, None); + let ident = if field.ident.name == kw::Underscore { + // We are processing delegation `Self` mapping field, we expect that it is a struct + // with one field, so if it is not true we emit an error as we can't infer + // what field to use to initialize the struct. + // The `_` is a keyword so if user uses it during struct initialization as a field name + // it will be error anyway. + if let [v_field] = &variant.fields[..] { + Ok(get_field_ident(v_field)) + } else { + Err(tcx.dcx().span_delayed_bug( + field.ident.span, + format!( + "can not infer the delegation `Self` mapping field for {:?}", + self.body_def_id + ), + )) } - - self.field_ty(field.span, v_field, args) } else { - error_happened = true; - let guar = if let Some(prev_span) = seen_fields.get(&ident) { - self.dcx().emit_err(FieldMultiplySpecifiedInInitializer { - span: field.ident.span, - prev_span: *prev_span, - ident, - }) - } else { - self.report_unknown_field( - adt_ty, - variant, - expr, - field, - hir_fields, - adt.variant_descr(), - ) - }; - - Ty::new_error(tcx, guar) + Ok(tcx.adjust_ident(field.ident, variant.def_id)) }; + let field_type = ident + .and_then(|ident| { + if let Some((i, v_field)) = remaining_fields.remove(&ident) { + seen_fields.insert(ident, field.span); + self.write_field_index(field.hir_id, i); + + // We don't look at stability attributes on + // struct-like enums (yet...), but it's definitely not + // a bug to have constructed one. + if adt_kind != AdtKind::Enum { + tcx.check_stability(v_field.did, Some(field.hir_id), field.span, None); + } + + Ok(self.field_ty(field.span, v_field, args)) + } else { + error_happened = true; + let guar = if let Some(prev_span) = seen_fields.get(&ident) { + self.dcx().emit_err(FieldMultiplySpecifiedInInitializer { + span: field.ident.span, + prev_span: *prev_span, + ident, + }) + } else { + self.report_unknown_field( + adt_ty, + variant, + expr, + field, + hir_fields, + adt.variant_descr(), + ) + }; + + Err(guar) + } + }) + .unwrap_or_else(|err| Ty::new_error(tcx, err)); + // Check that the expected field type is WF. Otherwise, we emit no use-site error // in the case of coercions for non-WF fields, which leads to incorrect error // tainting. See issue #126272. diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index 16a2ee9f22eaf..926efb9ccd7c0 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -35,7 +35,7 @@ use rustc_middle::ty::{ self, AssocContainer, Const, GenericParamDefKind, PredicateProxy, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, }; -use rustc_span::{Ident, Span, Symbol, bug, span_bug, sym}; +use rustc_span::{Ident, Span, Symbol, bug, kw, span_bug, sym}; use tracing::debug; //////////////////////////////////////////////////////////////////////////////// @@ -1077,7 +1077,14 @@ impl<'tcx> Visitor<'tcx> for NamePrivacyVisitor<'tcx> { for field in fields { let index = self.typeck_results().field_index(field.hir_id); if self.check_field(field.ident.span, adt, &variant.fields[index]) { - failed_fields.push((field.ident.name, field.ident.span, true)); + // kw::Underscore is a `Self` mapping delegation field which is + // generated by compiler, see delegation AST -> HIR lowering code + // and `check_expr_struct_fields` for details. + let name = match field.ident.name { + kw::Underscore => variant.fields[index].name, + name => name, + }; + failed_fields.push((name, field.ident.span, true)); } } self.emit_unreachable_field_error(failed_fields, adt, None, qpath.span()); diff --git a/tests/pretty/delegation/self-mapping-output.pp b/tests/pretty/delegation/self-mapping-output.pp index 5bce43315e1d0..3c77be7ed6831 100644 --- a/tests/pretty/delegation/self-mapping-output.pp +++ b/tests/pretty/delegation/self-mapping-output.pp @@ -24,7 +24,7 @@ struct W(S); impl Trait for W { #[attr = Inline(Hint)] - fn method(self: _) -> _ { from(Self { 0: Trait::method(self.0) }) } + fn method(self: _) -> _ { from(Self { _: Trait::method(self.0) }) } #[attr = Inline(Hint)] fn r#static() -> _ { Trait::r#static() } //~^ WARN: function cannot return without recursing [unconditional_recursion] @@ -34,7 +34,7 @@ impl W { #[attr = Inline(Hint)] - fn method(self: _) -> _ { from(Self { 0: Trait::method(self.0) }) } + fn method(self: _) -> _ { from(Self { _: Trait::method(self.0) }) } #[attr = Inline(Hint)] fn r#static() -> _ { Trait::r#static() } #[attr = Inline(Hint)] diff --git a/tests/ui/delegation/self-mapping-output-privacy.rs b/tests/ui/delegation/self-mapping-output-privacy.rs index f072d240608f8..ee5843884f268 100644 --- a/tests/ui/delegation/self-mapping-output-privacy.rs +++ b/tests/ui/delegation/self-mapping-output-privacy.rs @@ -13,6 +13,10 @@ impl Trait for S { mod private { pub struct W(super::S); + + pub struct W1 { + sss: super::S, + } } impl Trait for private::W { @@ -25,4 +29,14 @@ impl private::W { //~^ ERROR: field `0` of struct `W` is private } +impl Trait for private::W1 { + reuse Trait::method { S } + //~^ ERROR: field `sss` of struct `W1` is private +} + +impl private::W1 { + reuse Trait::method { S } + //~^ ERROR: field `sss` of struct `W1` is private +} + fn main() {} diff --git a/tests/ui/delegation/self-mapping-output-privacy.stderr b/tests/ui/delegation/self-mapping-output-privacy.stderr index 38f6b1a4a9ddf..113b43a2893f4 100644 --- a/tests/ui/delegation/self-mapping-output-privacy.stderr +++ b/tests/ui/delegation/self-mapping-output-privacy.stderr @@ -1,15 +1,27 @@ error[E0451]: field `0` of struct `W` is private - --> $DIR/self-mapping-output-privacy.rs:19:18 + --> $DIR/self-mapping-output-privacy.rs:23:18 | LL | reuse Trait::method { S } | ^^^^^^ private field error[E0451]: field `0` of struct `W` is private - --> $DIR/self-mapping-output-privacy.rs:24:18 + --> $DIR/self-mapping-output-privacy.rs:28:18 | LL | reuse Trait::method { S } | ^^^^^^ private field -error: aborting due to 2 previous errors +error[E0451]: field `sss` of struct `W1` is private + --> $DIR/self-mapping-output-privacy.rs:33:18 + | +LL | reuse Trait::method { S } + | ^^^^^^ private field + +error[E0451]: field `sss` of struct `W1` is private + --> $DIR/self-mapping-output-privacy.rs:38:18 + | +LL | reuse Trait::method { S } + | ^^^^^^ private field + +error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0451`. diff --git a/tests/ui/delegation/self-mapping-output.rs b/tests/ui/delegation/self-mapping-output.rs index 47b122b0accda..d20240bd6d88f 100644 --- a/tests/ui/delegation/self-mapping-output.rs +++ b/tests/ui/delegation/self-mapping-output.rs @@ -109,7 +109,6 @@ mod no_constructor { impl Trait for W { reuse Trait::method { self.0 } //~^ ERROR: no field `0` on type `&no_constructor::W` - //~| ERROR: struct `no_constructor::W` has no field named `0` reuse Trait::r#static; //~^ WARN: function cannot return without recursing [unconditional_recursion] reuse Trait::raw_S { self.0 } @@ -119,7 +118,6 @@ mod no_constructor { impl W { reuse Trait::method { self.0 } //~^ ERROR: no field `0` on type `&no_constructor::W` - //~| ERROR: struct `no_constructor::W` has no field named `0` reuse Trait::r#static; reuse Trait::raw_S { self.0 } //~^ ERROR: no field `0` on type `&no_constructor::W` @@ -142,7 +140,7 @@ mod more_than_one_field { struct W(S, S, S); impl Trait for W { reuse Trait::method { self.0 } - //~^ ERROR: missing fields `1` and `2` in initializer of `more_than_one_field::W` + //~^ ERROR: missing fields `0`, `1` and `2` in initializer of `more_than_one_field::W` reuse Trait::r#static; //~^ WARN: function cannot return without recursing [unconditional_recursion] reuse Trait::raw_S { self.0 } @@ -150,7 +148,7 @@ mod more_than_one_field { impl W { reuse Trait::method { self.0 } - //~^ ERROR: missing fields `1` and `2` in initializer of `more_than_one_field::W` + //~^ ERROR: missing fields `0`, `1` and `2` in initializer of `more_than_one_field::W` reuse Trait::r#static; reuse Trait::raw_S { self.0 } } diff --git a/tests/ui/delegation/self-mapping-output.stderr b/tests/ui/delegation/self-mapping-output.stderr index 86ce822a1821c..621ad70811381 100644 --- a/tests/ui/delegation/self-mapping-output.stderr +++ b/tests/ui/delegation/self-mapping-output.stderr @@ -1,15 +1,3 @@ -error[E0560]: struct `no_constructor::W` has no field named `0` - --> $DIR/self-mapping-output.rs:110:22 - | -LL | reuse Trait::method { self.0 } - | ^^^^^^ unknown field - | -help: a field with a similar name exists - | -LL - reuse Trait::method { self.0 } -LL + reuse Trait::s { self.0 } - | - error[E0609]: no field `0` on type `&no_constructor::W` --> $DIR/self-mapping-output.rs:110:36 | @@ -23,7 +11,7 @@ LL + reuse Trait::method { self.s } | error[E0609]: no field `0` on type `&no_constructor::W` - --> $DIR/self-mapping-output.rs:115:35 + --> $DIR/self-mapping-output.rs:114:35 | LL | reuse Trait::raw_S { self.0 } | ^ unknown field @@ -34,20 +22,8 @@ LL - reuse Trait::raw_S { self.0 } LL + reuse Trait::raw_S { self.s } | -error[E0560]: struct `no_constructor::W` has no field named `0` - --> $DIR/self-mapping-output.rs:120:22 - | -LL | reuse Trait::method { self.0 } - | ^^^^^^ unknown field - | -help: a field with a similar name exists - | -LL - reuse Trait::method { self.0 } -LL + reuse Trait::s { self.0 } - | - error[E0609]: no field `0` on type `&no_constructor::W` - --> $DIR/self-mapping-output.rs:120:36 + --> $DIR/self-mapping-output.rs:119:36 | LL | reuse Trait::method { self.0 } | ^ unknown field @@ -59,7 +35,7 @@ LL + reuse Trait::method { self.s } | error[E0609]: no field `0` on type `&no_constructor::W` - --> $DIR/self-mapping-output.rs:124:35 + --> $DIR/self-mapping-output.rs:122:35 | LL | reuse Trait::raw_S { self.0 } | ^ unknown field @@ -70,20 +46,20 @@ LL - reuse Trait::raw_S { self.0 } LL + reuse Trait::raw_S { self.s } | -error[E0063]: missing fields `1` and `2` in initializer of `more_than_one_field::W` - --> $DIR/self-mapping-output.rs:144:22 +error[E0063]: missing fields `0`, `1` and `2` in initializer of `more_than_one_field::W` + --> $DIR/self-mapping-output.rs:142:22 | LL | reuse Trait::method { self.0 } - | ^^^^^^ missing `1` and `2` + | ^^^^^^ missing `0`, `1` and `2` -error[E0063]: missing fields `1` and `2` in initializer of `more_than_one_field::W` - --> $DIR/self-mapping-output.rs:152:22 +error[E0063]: missing fields `0`, `1` and `2` in initializer of `more_than_one_field::W` + --> $DIR/self-mapping-output.rs:150:22 | LL | reuse Trait::method { self.0 } - | ^^^^^^ missing `1` and `2` + | ^^^^^^ missing `0`, `1` and `2` error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:184:25 + --> $DIR/self-mapping-output.rs:182:25 | LL | pub fn method(_: impl super::Trait) -> impl super::Trait { | ----------------- the found opaque type @@ -98,7 +74,7 @@ LL | reuse to_reuse::method { self.0 } found opaque type `impl non_trait_path_reuse::Trait` error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:186:25 + --> $DIR/self-mapping-output.rs:184:25 | LL | pub fn r#static() -> impl super::Trait { | ----------------- the found opaque type @@ -113,13 +89,13 @@ LL | reuse to_reuse::r#static; found opaque type `impl non_trait_path_reuse::Trait` error[E0609]: no field `0` on type `impl super::Trait` - --> $DIR/self-mapping-output.rs:191:39 + --> $DIR/self-mapping-output.rs:189:39 | LL | reuse to_reuse::method { self.0 } | ^ unknown field error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:213:31 + --> $DIR/self-mapping-output.rs:211:31 | LL | reuse Trait::method { self.0 } | ------ ^^^^^^ expected `&_`, found `()` @@ -129,7 +105,7 @@ LL | reuse Trait::method { self.0 } = note: expected reference `&_` found unit type `()` note: method defined here - --> $DIR/self-mapping-output.rs:199:12 + --> $DIR/self-mapping-output.rs:197:12 | LL | fn method(&self) -> (); | ^^^^^^ ---- @@ -139,14 +115,14 @@ LL | reuse Trait::method { &self.0 } | + error[E0283]: type annotations needed - --> $DIR/self-mapping-output.rs:216:22 + --> $DIR/self-mapping-output.rs:214:22 | LL | reuse Trait::r#static; | ^^^^^^^^ cannot infer type | = note: the type must implement `non_Self_return_type::Trait` help: the following types implement trait `non_Self_return_type::Trait` - --> $DIR/self-mapping-output.rs:205:5 + --> $DIR/self-mapping-output.rs:203:5 | LL | impl Trait for S { | ^^^^^^^^^^^^^^^^ `non_Self_return_type::S` @@ -155,7 +131,7 @@ LL | impl Trait for W { | ^^^^^^^^^^^^^^^^ `non_Self_return_type::W` error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:219:30 + --> $DIR/self-mapping-output.rs:217:30 | LL | reuse Trait::raw_S { self.0 } | ----- ^^^^^^ expected `&_`, found `()` @@ -165,7 +141,7 @@ LL | reuse Trait::raw_S { self.0 } = note: expected reference `&_` found unit type `()` note: method defined here - --> $DIR/self-mapping-output.rs:201:12 + --> $DIR/self-mapping-output.rs:199:12 | LL | fn raw_S(&self) -> S { S } | ^^^^^ ----- @@ -175,7 +151,7 @@ LL | reuse Trait::raw_S { &self.0 } | + error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:224:31 + --> $DIR/self-mapping-output.rs:222:31 | LL | reuse Trait::method { self.0 } | ------ ^^^^^^ expected `&_`, found `()` @@ -185,7 +161,7 @@ LL | reuse Trait::method { self.0 } = note: expected reference `&_` found unit type `()` note: method defined here - --> $DIR/self-mapping-output.rs:199:12 + --> $DIR/self-mapping-output.rs:197:12 | LL | fn method(&self) -> (); | ^^^^^^ ---- @@ -195,14 +171,14 @@ LL | reuse Trait::method { &self.0 } | + error[E0283]: type annotations needed - --> $DIR/self-mapping-output.rs:227:22 + --> $DIR/self-mapping-output.rs:225:22 | LL | reuse Trait::r#static; | ^^^^^^^^ cannot infer type | = note: the type must implement `non_Self_return_type::Trait` help: the following types implement trait `non_Self_return_type::Trait` - --> $DIR/self-mapping-output.rs:205:5 + --> $DIR/self-mapping-output.rs:203:5 | LL | impl Trait for S { | ^^^^^^^^^^^^^^^^ `non_Self_return_type::S` @@ -211,7 +187,7 @@ LL | impl Trait for W { | ^^^^^^^^^^^^^^^^ `non_Self_return_type::W` error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:230:30 + --> $DIR/self-mapping-output.rs:228:30 | LL | reuse Trait::raw_S { self.0 } | ----- ^^^^^^ expected `&_`, found `()` @@ -221,7 +197,7 @@ LL | reuse Trait::raw_S { self.0 } = note: expected reference `&_` found unit type `()` note: method defined here - --> $DIR/self-mapping-output.rs:201:12 + --> $DIR/self-mapping-output.rs:199:12 | LL | fn raw_S(&self) -> S { S } | ^^^^^ ----- @@ -231,7 +207,7 @@ LL | reuse Trait::raw_S { &self.0 } | + error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:256:38 + --> $DIR/self-mapping-output.rs:254:38 | LL | reuse ::method { self.0 } | ------ ^^^^^^ expected `&F`, found `&S` @@ -242,19 +218,19 @@ LL | reuse ::method { self.0 } = note: expected reference `&wrong_return_type::F` found reference `&wrong_return_type::S` note: method defined here - --> $DIR/self-mapping-output.rs:237:12 + --> $DIR/self-mapping-output.rs:235:12 | LL | fn method(&self) -> Self; | ^^^^^^ ---- error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:256:29 + --> $DIR/self-mapping-output.rs:254:29 | LL | reuse ::method { self.0 } | ^^^^^^ expected `S`, found `F` error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:260:29 + --> $DIR/self-mapping-output.rs:258:29 | LL | reuse ::r#static; | ^^^^^^^^ @@ -263,7 +239,7 @@ LL | reuse ::r#static; | expected `wrong_return_type::W` because of return type error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:263:37 + --> $DIR/self-mapping-output.rs:261:37 | LL | reuse ::raw_S { self.0 } | ----- ^^^^^^ expected `&F`, found `&S` @@ -273,13 +249,13 @@ LL | reuse ::raw_S { self.0 } = note: expected reference `&wrong_return_type::F` found reference `&wrong_return_type::S` note: method defined here - --> $DIR/self-mapping-output.rs:239:12 + --> $DIR/self-mapping-output.rs:237:12 | LL | fn raw_S(&self) -> S { S } | ^^^^^ ----- error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:268:38 + --> $DIR/self-mapping-output.rs:266:38 | LL | reuse ::method { self.0 } | ------ ^^^^^^ expected `&F`, found `&S` @@ -290,19 +266,19 @@ LL | reuse ::method { self.0 } = note: expected reference `&wrong_return_type::F` found reference `&wrong_return_type::S` note: method defined here - --> $DIR/self-mapping-output.rs:237:12 + --> $DIR/self-mapping-output.rs:235:12 | LL | fn method(&self) -> Self; | ^^^^^^ ---- error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:268:29 + --> $DIR/self-mapping-output.rs:266:29 | LL | reuse ::method { self.0 } | ^^^^^^ expected `S`, found `F` error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:272:29 + --> $DIR/self-mapping-output.rs:270:29 | LL | reuse ::r#static; | ^^^^^^^^ @@ -311,7 +287,7 @@ LL | reuse ::r#static; | expected `wrong_return_type::W` because of return type error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:275:37 + --> $DIR/self-mapping-output.rs:273:37 | LL | reuse ::raw_S { self.0 } | ----- ^^^^^^ expected `&F`, found `&S` @@ -321,13 +297,13 @@ LL | reuse ::raw_S { self.0 } = note: expected reference `&wrong_return_type::F` found reference `&wrong_return_type::S` note: method defined here - --> $DIR/self-mapping-output.rs:239:12 + --> $DIR/self-mapping-output.rs:237:12 | LL | fn raw_S(&self) -> S { S } | ^^^^^ ----- error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:301:31 + --> $DIR/self-mapping-output.rs:299:31 | LL | reuse Trait::method { F } | ------ ^ expected `&S`, found `&F` @@ -338,13 +314,13 @@ LL | reuse Trait::method { F } = note: expected reference `&wrong_target_expression::S` found reference `&wrong_target_expression::F` note: method defined here - --> $DIR/self-mapping-output.rs:282:12 + --> $DIR/self-mapping-output.rs:280:12 | LL | fn method(&self) -> Self; | ^^^^^^ ---- error[E0308]: mismatched types - --> $DIR/self-mapping-output.rs:310:31 + --> $DIR/self-mapping-output.rs:308:31 | LL | reuse Trait::method { F } | ------ ^ expected `&S`, found `&F` @@ -355,31 +331,31 @@ LL | reuse Trait::method { F } = note: expected reference `&wrong_target_expression::S` found reference `&wrong_target_expression::F` note: method defined here - --> $DIR/self-mapping-output.rs:282:12 + --> $DIR/self-mapping-output.rs:280:12 | LL | fn method(&self) -> Self; | ^^^^^^ ---- error[E0616]: field `0` of struct `private::W` is private - --> $DIR/self-mapping-output.rs:336:36 + --> $DIR/self-mapping-output.rs:334:36 | LL | reuse Trait::method { self.0 } | ^ private field error[E0616]: field `0` of struct `private::W` is private - --> $DIR/self-mapping-output.rs:342:35 + --> $DIR/self-mapping-output.rs:340:35 | LL | reuse Trait::raw_S { self.0 } | ^ private field error[E0616]: field `0` of struct `private::W` is private - --> $DIR/self-mapping-output.rs:347:36 + --> $DIR/self-mapping-output.rs:345:36 | LL | reuse Trait::method { self.0 } | ^ private field error[E0616]: field `0` of struct `private::W` is private - --> $DIR/self-mapping-output.rs:352:35 + --> $DIR/self-mapping-output.rs:350:35 | LL | reuse Trait::raw_S { self.0 } | ^ private field @@ -419,7 +395,7 @@ LL | reuse Trait::<'a, T, N>::r#static; = help: a `loop` may express intention better if this is on purpose warning: function cannot return without recursing - --> $DIR/self-mapping-output.rs:113:22 + --> $DIR/self-mapping-output.rs:112:22 | LL | reuse Trait::r#static; | ^^^^^^^^ @@ -430,7 +406,7 @@ LL | reuse Trait::r#static; = help: a `loop` may express intention better if this is on purpose warning: function cannot return without recursing - --> $DIR/self-mapping-output.rs:146:22 + --> $DIR/self-mapping-output.rs:144:22 | LL | reuse Trait::r#static; | ^^^^^^^^ @@ -441,7 +417,7 @@ LL | reuse Trait::r#static; = help: a `loop` may express intention better if this is on purpose warning: function cannot return without recursing - --> $DIR/self-mapping-output.rs:304:22 + --> $DIR/self-mapping-output.rs:302:22 | LL | reuse Trait::r#static; | ^^^^^^^^ @@ -452,7 +428,7 @@ LL | reuse Trait::r#static; = help: a `loop` may express intention better if this is on purpose warning: function cannot return without recursing - --> $DIR/self-mapping-output.rs:339:22 + --> $DIR/self-mapping-output.rs:337:22 | LL | reuse Trait::r#static; | ^^^^^^^^ @@ -462,7 +438,7 @@ LL | reuse Trait::r#static; | = help: a `loop` may express intention better if this is on purpose -error: aborting due to 31 previous errors; 7 warnings emitted +error: aborting due to 29 previous errors; 7 warnings emitted -Some errors have detailed explanations: E0063, E0283, E0308, E0560, E0609, E0616. +Some errors have detailed explanations: E0063, E0283, E0308, E0609, E0616. For more information about an error, try `rustc --explain E0063`. diff --git a/tests/ui/delegation/self-type-mapping-field-name.rs b/tests/ui/delegation/self-type-mapping-field-name.rs new file mode 100644 index 0000000000000..c57f701801da7 --- /dev/null +++ b/tests/ui/delegation/self-type-mapping-field-name.rs @@ -0,0 +1,56 @@ +//@ check-pass + +#![feature(fn_delegation)] + +mod simple_test { + trait Trait { + fn foo(&self) -> Self; + } + + struct S; + impl Trait for S { + fn foo(&self) -> S { S } + } + + struct W { s: S } + impl Trait for W { + reuse Trait::foo { self.s } + } + + impl W { + reuse Trait::foo { self.s } + } + + struct W1(S); + impl Trait for W1 { + reuse Trait::foo { self.0 } + } + + impl W1 { + reuse Trait::foo { self.0 } + } +} + +mod rfc { + struct BTreeMap { + _inner: std::collections::HashMap + } + + impl Clone for BTreeMap { + fn clone(&self) -> Self { + panic!() + } + + fn clone_from(&mut self, source: &Self) { + panic!() + } + } + + struct BTreeSet { + map: BTreeMap + } + + reuse impl Clone for BTreeSet { self.map } +} + +fn main() {}