From a5e4dc9a9abcf10b36f1fd1fef2051b13aa0098a Mon Sep 17 00:00:00 2001 From: Pieter-Louis Schoeman Date: Thu, 28 May 2026 16:23:43 +0000 Subject: [PATCH 1/7] Handle generic reborrow in expression-use adjustment walking * Handle generic reborrow in expression-use adjustment walking * Require generic reborrow to be terminal in adjustment walks --- .../rustc_hir_typeck/src/expr_use_visitor.rs | 17 +++++----- ...neric-reborrow-expr-use-visitor-closure.rs | 34 +++++++++++++++++++ .../generic-reborrow-expr-use-visitor.rs | 32 +++++++++++++++++ 3 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 tests/ui/reborrow/generic-reborrow-expr-use-visitor-closure.rs create mode 100644 tests/ui/reborrow/generic-reborrow-expr-use-visitor.rs diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index a924a81f89b0d..7b39b953e3be0 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -727,7 +727,8 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx let typeck_results = self.cx.typeck_results(); let adjustments = typeck_results.expr_adjustments(expr); let mut place_with_id = self.cat_expr_unadjusted(expr)?; - for adjustment in adjustments { + for (adjustment_index, adjustment) in adjustments.iter().enumerate() { + let is_last_adjustment = adjustment_index + 1 == adjustments.len(); debug!("walk_adjustment expr={:?} adj={:?}", expr, adjustment); match adjustment.kind { adjustment::Adjust::NeverToAny | adjustment::Adjust::Pointer(_) => { @@ -752,13 +753,13 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx self.walk_autoref(expr, &place_with_id, autoref); } - adjustment::Adjust::GenericReborrow(_reborrow) => { - // To build an expression as a place expression, it needs to be a field - // projection or deref at the outmost layer. So it is field projection or deref - // on an adjusted value. But this means that adjustment is applied on a - // subexpression that is not the final operand/rvalue for function call or - // assignment. This is a contradiction. - unreachable!("Reborrow trait usage during adjustment walk"); + adjustment::Adjust::GenericReborrow(mutability) if is_last_adjustment => { + let bk = ty::BorrowKind::from_mutbl(mutability); + self.delegate.borrow_mut().borrow(&place_with_id, place_with_id.hir_id, bk); + } + + adjustment::Adjust::GenericReborrow(_) => { + span_bug!(expr.span, "generic reborrow adjustment must be terminal"); } } place_with_id = self.cat_expr_adjusted(expr, place_with_id, adjustment)?; diff --git a/tests/ui/reborrow/generic-reborrow-expr-use-visitor-closure.rs b/tests/ui/reborrow/generic-reborrow-expr-use-visitor-closure.rs new file mode 100644 index 0000000000000..c28cb1ef4fda4 --- /dev/null +++ b/tests/ui/reborrow/generic-reborrow-expr-use-visitor-closure.rs @@ -0,0 +1,34 @@ +//@ check-pass + +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +#[allow(unused)] +struct CustomMut<'a, T>(&'a mut T); +impl<'a, T> Reborrow for CustomMut<'a, T> {} +impl<'a, T> CoerceShared> for CustomMut<'a, T> {} + +#[allow(unused)] +struct CustomRef<'a, T>(&'a T); +impl<'a, T> Clone for CustomRef<'a, T> { + fn clone(&self) -> Self { + Self(self.0) + } +} +impl<'a, T> Copy for CustomRef<'a, T> {} + +fn takes_mut(_: CustomMut<'_, ()>) {} +fn takes_shared(_: CustomRef<'_, ()>) {} + +fn main() { + let a = CustomMut(&mut ()); + + let mut f = || { + takes_mut(a); + takes_shared(a); + }; + + f(); + f(); +} diff --git a/tests/ui/reborrow/generic-reborrow-expr-use-visitor.rs b/tests/ui/reborrow/generic-reborrow-expr-use-visitor.rs new file mode 100644 index 0000000000000..d513577e901a0 --- /dev/null +++ b/tests/ui/reborrow/generic-reborrow-expr-use-visitor.rs @@ -0,0 +1,32 @@ +//@ check-pass + +#![feature(reborrow)] + +use std::marker::{CoerceShared, Reborrow}; + +#[allow(unused)] +struct CustomMut<'a, T>(&'a mut T); +impl<'a, T> Reborrow for CustomMut<'a, T> {} +impl<'a, T> CoerceShared> for CustomMut<'a, T> {} + +#[allow(unused)] +struct CustomRef<'a, T>(&'a T); +impl<'a, T> Clone for CustomRef<'a, T> { + fn clone(&self) -> Self { + Self(self.0) + } +} +impl<'a, T> Copy for CustomRef<'a, T> {} + +fn takes_mut(_: CustomMut<'_, ()>) {} +fn takes_shared(_: CustomRef<'_, ()>) {} + +fn main() { + let a = CustomMut(&mut ()); + + takes_mut(a); + takes_mut(a); + + takes_shared(a); + takes_shared(a); +} From b8d7dcb0696417e0190e8740ee3d9da8f7a16e54 Mon Sep 17 00:00:00 2001 From: Kevin Valerio Date: Wed, 17 Jun 2026 17:06:42 +0200 Subject: [PATCH 2/7] fix(thir): visit reborrow source expressions --- compiler/rustc_middle/src/thir/visit.rs | 2 +- tests/ui/reborrow/reborrow-source-unsafety.rs | 28 +++++++++++++++++++ .../reborrow/reborrow-source-unsafety.stderr | 11 ++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 tests/ui/reborrow/reborrow-source-unsafety.rs create mode 100644 tests/ui/reborrow/reborrow-source-unsafety.stderr diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs index a96f4e9457cb0..f1f3e2d98b226 100644 --- a/compiler/rustc_middle/src/thir/visit.rs +++ b/compiler/rustc_middle/src/thir/visit.rs @@ -187,7 +187,7 @@ pub fn walk_expr<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>( } ThreadLocalRef(_) => {} Yield { value } => visitor.visit_expr(&visitor.thir()[value]), - Reborrow { .. } => {} + Reborrow { source, .. } => visitor.visit_expr(&visitor.thir()[source]), } } diff --git a/tests/ui/reborrow/reborrow-source-unsafety.rs b/tests/ui/reborrow/reborrow-source-unsafety.rs new file mode 100644 index 0000000000000..e6f3540768a44 --- /dev/null +++ b/tests/ui/reborrow/reborrow-source-unsafety.rs @@ -0,0 +1,28 @@ +// Regression test for rust-lang/rust#158033. + +#![feature(reborrow)] +#![allow(dead_code)] +#![deny(unsafe_code)] + +use std::marker::Reborrow; + +struct Thing<'a> { + field: &'a mut usize, +} + +impl<'a> Reborrow for Thing<'a> {} + +fn takes(_: Thing<'_>) {} + +fn main() { + let mut x = 0; + let thing = Thing { field: &mut x }; + let y = 123usize; + + takes({ + let p: *const usize = &y; + std::hint::black_box(std::ptr::read(p)); + //~^ ERROR call to unsafe function `std::ptr::read` is unsafe + thing + }); +} diff --git a/tests/ui/reborrow/reborrow-source-unsafety.stderr b/tests/ui/reborrow/reborrow-source-unsafety.stderr new file mode 100644 index 0000000000000..431893a4d0ced --- /dev/null +++ b/tests/ui/reborrow/reborrow-source-unsafety.stderr @@ -0,0 +1,11 @@ +error[E0133]: call to unsafe function `std::ptr::read` is unsafe and requires unsafe function or block + --> $DIR/reborrow-source-unsafety.rs:24:30 + | +LL | std::hint::black_box(std::ptr::read(p)); + | ^^^^^^^^^^^^^^^^^ call to unsafe function + | + = note: consult the function's documentation for information on how to avoid undefined behavior + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0133`. From a487aec39ae75c92fe6f0e3dccb83f07c224c5ef Mon Sep 17 00:00:00 2001 From: Kevin Valerio <24193167+kevin-valerio@users.noreply.github.com> Date: Wed, 17 Jun 2026 17:58:52 +0200 Subject: [PATCH 3/7] Match all fields and removing `..` Co-authored-by: Timo <30553356+y21@users.noreply.github.com> --- compiler/rustc_middle/src/thir/visit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs index f1f3e2d98b226..e3366e74b5f26 100644 --- a/compiler/rustc_middle/src/thir/visit.rs +++ b/compiler/rustc_middle/src/thir/visit.rs @@ -187,7 +187,7 @@ pub fn walk_expr<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>( } ThreadLocalRef(_) => {} Yield { value } => visitor.visit_expr(&visitor.thir()[value]), - Reborrow { source, .. } => visitor.visit_expr(&visitor.thir()[source]), + Reborrow { source, mutability: _, target: _ } => visitor.visit_expr(&visitor.thir()[source]), } } From ef672ad0120cab59576e245df472bd662af29e45 Mon Sep 17 00:00:00 2001 From: Kevin Valerio Date: Wed, 17 Jun 2026 18:14:59 +0200 Subject: [PATCH 4/7] format reborrow visitor arm --- compiler/rustc_middle/src/thir/visit.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs index e3366e74b5f26..24aa4ac513d45 100644 --- a/compiler/rustc_middle/src/thir/visit.rs +++ b/compiler/rustc_middle/src/thir/visit.rs @@ -187,7 +187,9 @@ pub fn walk_expr<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>( } ThreadLocalRef(_) => {} Yield { value } => visitor.visit_expr(&visitor.thir()[value]), - Reborrow { source, mutability: _, target: _ } => visitor.visit_expr(&visitor.thir()[source]), + Reborrow { source, mutability: _, target: _ } => { + visitor.visit_expr(&visitor.thir()[source]) + } } } From 36f5b3f260e6e16a5e32fd50f4946e760493b32a Mon Sep 17 00:00:00 2001 From: Valentyn Kit Date: Wed, 17 Jun 2026 22:58:37 +0300 Subject: [PATCH 5/7] Document transient connection errors from TcpListener::accept `accept` can return an error that belongs to a single incoming connection, not to the listener itself, for example a connection aborted by the peer before it could be accepted. The listener stays usable in that case, so code serving a long-lived listener usually wants to log the error and keep accepting connections rather than treat it as fatal. This was previously undocumented. - Add an `# Errors` section to `accept` that describes this behavior without listing specific error codes. - Note that `Interrupted` errors are retried internally on Unix. - Point `incoming` and `into_incoming` at `accept` for the same details. --- library/std/src/net/tcp.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/library/std/src/net/tcp.rs b/library/std/src/net/tcp.rs index 2e8779e05ca76..53bef0b4de609 100644 --- a/library/std/src/net/tcp.rs +++ b/library/std/src/net/tcp.rs @@ -876,6 +876,22 @@ impl TcpListener { /// is established. When established, the corresponding [`TcpStream`] and the /// remote peer's address will be returned. /// + /// # Errors + /// + /// Some errors returned by this function relate to a single incoming + /// connection that failed before it could be accepted, such as one aborted + /// by the peer ([`ConnectionAborted`]). Such an error does not indicate a + /// problem with the listener itself, which remains usable. Code serving a + /// long-lived listener will usually want to log the error and continue + /// accepting connections rather than treat it as fatal. Which errors can + /// occur this way is platform-specific. + /// + /// On Unix, [`Interrupted`] errors are retried internally rather than being + /// returned. + /// + /// [`ConnectionAborted`]: io::ErrorKind::ConnectionAborted + /// [`Interrupted`]: io::ErrorKind::Interrupted + /// /// # Examples /// /// ```no_run @@ -902,6 +918,11 @@ impl TcpListener { /// the peer's [`SocketAddr`] structure. Iterating over it is equivalent to /// calling [`TcpListener::accept`] in a loop. /// + /// # Errors + /// + /// Each connection yielded by the iterator can fail for the same reasons as + /// [`TcpListener::accept`]; see its documentation for details. + /// /// # Examples /// /// ```no_run @@ -937,6 +958,11 @@ impl TcpListener { /// the peer's [`SocketAddr`] structure. Iterating over it is equivalent to /// calling [`TcpListener::accept`] in a loop. /// + /// # Errors + /// + /// Each connection yielded by the iterator can fail for the same reasons as + /// [`TcpListener::accept`]; see its documentation for details. + /// /// # Examples /// /// ```no_run From 34d3eed3faa8250cb5ad0aea5e86b9c95eb4e0d9 Mon Sep 17 00:00:00 2001 From: Valentyn Kit Date: Thu, 18 Jun 2026 14:50:54 +0300 Subject: [PATCH 6/7] Document the file-descriptor-limit error from TcpListener::accept --- library/std/src/net/tcp.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/library/std/src/net/tcp.rs b/library/std/src/net/tcp.rs index 53bef0b4de609..4e67903540a65 100644 --- a/library/std/src/net/tcp.rs +++ b/library/std/src/net/tcp.rs @@ -878,16 +878,20 @@ impl TcpListener { /// /// # Errors /// - /// Some errors returned by this function relate to a single incoming - /// connection that failed before it could be accepted, such as one aborted - /// by the peer ([`ConnectionAborted`]). Such an error does not indicate a - /// problem with the listener itself, which remains usable. Code serving a - /// long-lived listener will usually want to log the error and continue - /// accepting connections rather than treat it as fatal. Which errors can - /// occur this way is platform-specific. - /// - /// On Unix, [`Interrupted`] errors are retried internally rather than being - /// returned. + /// Some errors this function returns do not indicate a problem with the + /// listener itself, and a program serving a long-lived listener will + /// usually want to handle them and keep accepting connections rather than + /// treat them as fatal. These include, but are not limited to: + /// + /// - An error specific to a single incoming connection that failed before + /// it could be accepted, such as one aborted by the peer + /// ([`ConnectionAborted`]). A later call may succeed immediately. + /// - An error from reaching the per-process or system-wide open file + /// descriptor limit. The call can be retried once other file descriptors + /// have been closed, typically after a short delay. + /// + /// Which errors can occur is platform-specific. On Unix, [`Interrupted`] + /// errors are retried internally rather than being returned. /// /// [`ConnectionAborted`]: io::ErrorKind::ConnectionAborted /// [`Interrupted`]: io::ErrorKind::Interrupted From 14475190e56c5b7fbf8f5d5c42327b89f65f4480 Mon Sep 17 00:00:00 2001 From: Valentyn Kit Date: Thu, 18 Jun 2026 16:04:05 +0300 Subject: [PATCH 7/7] Document the out-of-memory error from TcpListener::accept --- library/std/src/net/tcp.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/std/src/net/tcp.rs b/library/std/src/net/tcp.rs index 4e67903540a65..b673abdff7ba1 100644 --- a/library/std/src/net/tcp.rs +++ b/library/std/src/net/tcp.rs @@ -889,11 +889,14 @@ impl TcpListener { /// - An error from reaching the per-process or system-wide open file /// descriptor limit. The call can be retried once other file descriptors /// have been closed, typically after a short delay. + /// - An error from failing to allocate memory while accepting a connection + /// ([`OutOfMemory`]). /// /// Which errors can occur is platform-specific. On Unix, [`Interrupted`] /// errors are retried internally rather than being returned. /// /// [`ConnectionAborted`]: io::ErrorKind::ConnectionAborted + /// [`OutOfMemory`]: io::ErrorKind::OutOfMemory /// [`Interrupted`]: io::ErrorKind::Interrupted /// /// # Examples