From 0bd28eaf0c4f3a2cf1f7ae33b300c123b9097681 Mon Sep 17 00:00:00 2001 From: Andrew Tribick Date: Sun, 19 Jul 2026 09:50:03 +0200 Subject: [PATCH 01/28] feat: Update method signature of int_from_ascii --- library/core/src/num/mod.rs | 37 +++++++++++------ library/core/src/num/nonzero.rs | 31 ++++++++++---- library/coretests/tests/nonzero.rs | 41 ++++++++++--------- library/coretests/tests/num/mod.rs | 2 +- .../src/sys/platform_version/darwin/mod.rs | 10 ++--- tests/ui/consts/const-eval/parse_ints.stderr | 8 ++-- 6 files changed, 79 insertions(+), 50 deletions(-) diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index 59dfe6bd8d9b7..478af470637d7 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -1575,10 +1575,10 @@ pub const fn can_not_overflow(radix: u32, is_signed_ty: bool, digits: &[u8]) #[cfg_attr(panic = "immediate-abort", inline)] #[cold] #[track_caller] -const fn from_ascii_radix_panic(radix: u32) -> ! { +const fn from_ascii_bytes_radix_panic(radix: u32) -> ! { const_panic!( - "from_ascii_radix: radix must lie in the range `[2, 36]`", - "from_ascii_radix: radix must lie in the range `[2, 36]` - found {radix}", + "from_ascii_bytes_radix: radix must lie in the range `[2, 36]`", + "from_ascii_bytes_radix: radix must lie in the range `[2, 36]` - found {radix}", radix: u32 = radix, ) } @@ -1676,7 +1676,7 @@ macro_rules! from_str_int_impl { #[rustc_const_stable(feature = "const_int_from_str", since = "1.82.0")] #[inline] pub const fn from_str_radix(src: &str, radix: u32) -> Result<$int_ty, ParseIntError> { - <$int_ty>::from_ascii_radix(src.as_bytes(), radix) + <$int_ty>::from_ascii_bytes_radix_impl(src.as_bytes(), radix) } /// Parses an integer from an ASCII-byte slice with decimal digits. @@ -1700,18 +1700,22 @@ macro_rules! from_str_int_impl { /// ``` /// #![feature(int_from_ascii)] /// - #[doc = concat!("assert_eq!(", stringify!($int_ty), "::from_ascii(b\"+10\"), Ok(10));")] + #[doc = concat!("assert_eq!(", stringify!($int_ty), "::from_ascii_bytes(b\"+10\"), Ok(10));")] /// ``` /// Trailing space returns error: /// ``` /// # #![feature(int_from_ascii)] /// # - #[doc = concat!("assert!(", stringify!($int_ty), "::from_ascii(b\"1 \").is_err());")] + #[doc = concat!("assert!(", stringify!($int_ty), "::from_ascii_bytes(b\"1 \").is_err());")] /// ``` #[unstable(feature = "int_from_ascii", issue = "134821")] + #[rustc_const_unstable(feature = "const_convert", issue = "143773")] #[inline] - pub const fn from_ascii(src: &[u8]) -> Result<$int_ty, ParseIntError> { - <$int_ty>::from_ascii_radix(src, 10) + pub const fn from_ascii_bytes(src: T) -> Result<$int_ty, ParseIntError> + where + T: [const] AsRef<[u8]> + [const] crate::marker::Destruct + { + <$int_ty>::from_ascii_bytes_radix(src.as_ref(), 10) } /// Parses an integer from an ASCII-byte slice with digits in a given base. @@ -1744,22 +1748,31 @@ macro_rules! from_str_int_impl { /// ``` /// #![feature(int_from_ascii)] /// - #[doc = concat!("assert_eq!(", stringify!($int_ty), "::from_ascii_radix(b\"A\", 16), Ok(10));")] + #[doc = concat!("assert_eq!(", stringify!($int_ty), "::from_ascii_bytes_radix(b\"A\", 16), Ok(10));")] /// ``` /// Trailing space returns error: /// ``` /// # #![feature(int_from_ascii)] /// # - #[doc = concat!("assert!(", stringify!($int_ty), "::from_ascii_radix(b\"1 \", 10).is_err());")] + #[doc = concat!("assert!(", stringify!($int_ty), "::from_ascii_bytes_radix(b\"1 \", 10).is_err());")] /// ``` #[unstable(feature = "int_from_ascii", issue = "134821")] + #[rustc_const_unstable(feature = "const_convert", issue = "143773")] #[inline] - pub const fn from_ascii_radix(src: &[u8], radix: u32) -> Result<$int_ty, ParseIntError> { + pub const fn from_ascii_bytes_radix(src: T, radix: u32) -> Result<$int_ty, ParseIntError> + where + T: [const] AsRef<[u8]> + [const] crate::marker::Destruct + { + <$int_ty>::from_ascii_bytes_radix_impl(src.as_ref(), radix) + } + + #[inline] + pub(super) const fn from_ascii_bytes_radix_impl(src: &[u8], radix: u32) -> Result<$int_ty, ParseIntError> { use self::IntErrorKind::*; use self::ParseIntError as PIE; if 2 > radix || radix > 36 { - from_ascii_radix_panic(radix); + from_ascii_bytes_radix_panic(radix); } if src.is_empty() { diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index 0ce2c044a2dfd..e747ac591a3e2 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -1262,7 +1262,7 @@ macro_rules! nonzero_integer { /// # /// # fn main() { test().unwrap(); } /// # fn test() -> Option<()> { - #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::from_ascii(b\"+10\"), Ok(NonZero::new(10)?));")] + #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::from_ascii_bytes(b\"+10\"), Ok(NonZero::new(10)?));")] /// # Some(()) /// # } /// ``` @@ -1274,12 +1274,16 @@ macro_rules! nonzero_integer { /// /// # use std::num::NonZero; /// # - #[doc = concat!("assert!(NonZero::<", stringify!($Int), ">::from_ascii(b\"1 \").is_err());")] + #[doc = concat!("assert!(NonZero::<", stringify!($Int), ">::from_ascii_bytes(b\"1 \").is_err());")] /// ``` #[unstable(feature = "int_from_ascii", issue = "134821")] + #[rustc_const_unstable(feature = "const_convert", issue = "143773")] #[inline] - pub const fn from_ascii(src: &[u8]) -> Result { - Self::from_ascii_radix(src, 10) + pub const fn from_ascii_bytes(src: T) -> Result + where + T: [const] AsRef<[u8]> + [const] crate::marker::Destruct + { + Self::from_ascii_bytes_radix_impl(src.as_ref(), 10) } /// Parses a non-zero integer from an ASCII-byte slice with digits in a given base. @@ -1317,7 +1321,7 @@ macro_rules! nonzero_integer { /// # /// # fn main() { test().unwrap(); } /// # fn test() -> Option<()> { - #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::from_ascii_radix(b\"A\", 16), Ok(NonZero::new(10)?));")] + #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::from_ascii_bytes_radix(b\"A\", 16), Ok(NonZero::new(10)?));")] /// # Some(()) /// # } /// ``` @@ -1329,12 +1333,21 @@ macro_rules! nonzero_integer { /// /// # use std::num::NonZero; /// # - #[doc = concat!("assert!(NonZero::<", stringify!($Int), ">::from_ascii_radix(b\"1 \", 10).is_err());")] + #[doc = concat!("assert!(NonZero::<", stringify!($Int), ">::from_ascii_bytes_radix(b\"1 \", 10).is_err());")] /// ``` #[unstable(feature = "int_from_ascii", issue = "134821")] + #[rustc_const_unstable(feature = "const_convert", issue = "143773")] + #[inline] + pub const fn from_ascii_bytes_radix(src: T, radix: u32) -> Result + where + T: [const] AsRef<[u8]> + [const] crate::marker::Destruct + { + Self::from_ascii_bytes_radix_impl(src.as_ref(), radix) + } + #[inline] - pub const fn from_ascii_radix(src: &[u8], radix: u32) -> Result { - let n = match <$Int>::from_ascii_radix(src, radix) { + const fn from_ascii_bytes_radix_impl(src: &[u8], radix: u32) -> Result { + let n = match <$Int>::from_ascii_bytes_radix_impl(src, radix) { Ok(n) => n, Err(err) => return Err(err), }; @@ -1394,7 +1407,7 @@ macro_rules! nonzero_integer { #[rustc_const_stable(feature = "nonzero_from_str_radix", since = "1.98.0")] #[inline] pub const fn from_str_radix(src: &str, radix: u32) -> Result { - Self::from_ascii_radix(src.as_bytes(), radix) + Self::from_ascii_bytes_radix_impl(src.as_bytes(), radix) } } diff --git a/library/coretests/tests/nonzero.rs b/library/coretests/tests/nonzero.rs index 55d479efb4b40..5efc09fd2d98e 100644 --- a/library/coretests/tests/nonzero.rs +++ b/library/coretests/tests/nonzero.rs @@ -125,57 +125,60 @@ fn test_from_signed_nonzero() { } #[test] -fn test_from_ascii_radix() { - assert_eq!(NonZero::::from_ascii_radix(b"123", 10), Ok(NonZero::new(123).unwrap())); - assert_eq!(NonZero::::from_ascii_radix(b"1001", 2), Ok(NonZero::new(9).unwrap())); - assert_eq!(NonZero::::from_ascii_radix(b"123", 8), Ok(NonZero::new(83).unwrap())); - assert_eq!(NonZero::::from_ascii_radix(b"123", 16), Ok(NonZero::new(291).unwrap())); - assert_eq!(NonZero::::from_ascii_radix(b"ffff", 16), Ok(NonZero::new(65535).unwrap())); - assert_eq!(NonZero::::from_ascii_radix(b"z", 36), Ok(NonZero::new(35).unwrap())); +fn test_from_ascii_bytes_radix() { + assert_eq!(NonZero::::from_ascii_bytes_radix(b"123", 10), Ok(NonZero::new(123).unwrap())); + assert_eq!(NonZero::::from_ascii_bytes_radix(b"1001", 2), Ok(NonZero::new(9).unwrap())); + assert_eq!(NonZero::::from_ascii_bytes_radix(b"123", 8), Ok(NonZero::new(83).unwrap())); + assert_eq!(NonZero::::from_ascii_bytes_radix(b"123", 16), Ok(NonZero::new(291).unwrap())); assert_eq!( - NonZero::::from_ascii_radix(b"0", 10).err().map(|e| e.kind().clone()), + NonZero::::from_ascii_bytes_radix(b"ffff", 16), + Ok(NonZero::new(65535).unwrap()) + ); + assert_eq!(NonZero::::from_ascii_bytes_radix(b"z", 36), Ok(NonZero::new(35).unwrap())); + assert_eq!( + NonZero::::from_ascii_bytes_radix(b"0", 10).err().map(|e| e.kind().clone()), Some(IntErrorKind::Zero) ); assert_eq!( - NonZero::::from_ascii_radix(b"-1", 10).err().map(|e| e.kind().clone()), + NonZero::::from_ascii_bytes_radix(b"-1", 10).err().map(|e| e.kind().clone()), Some(IntErrorKind::InvalidDigit) ); assert_eq!( - NonZero::::from_ascii_radix(b"-129", 10).err().map(|e| e.kind().clone()), + NonZero::::from_ascii_bytes_radix(b"-129", 10).err().map(|e| e.kind().clone()), Some(IntErrorKind::NegOverflow) ); assert_eq!( - NonZero::::from_ascii_radix(b"257", 10).err().map(|e| e.kind().clone()), + NonZero::::from_ascii_bytes_radix(b"257", 10).err().map(|e| e.kind().clone()), Some(IntErrorKind::PosOverflow) ); assert_eq!( - NonZero::::from_ascii_radix(b"Z", 10).err().map(|e| e.kind().clone()), + NonZero::::from_ascii_bytes_radix(b"Z", 10).err().map(|e| e.kind().clone()), Some(IntErrorKind::InvalidDigit) ); assert_eq!( - NonZero::::from_ascii_radix(b"_", 2).err().map(|e| e.kind().clone()), + NonZero::::from_ascii_bytes_radix(b"_", 2).err().map(|e| e.kind().clone()), Some(IntErrorKind::InvalidDigit) ); } #[test] -fn test_from_ascii() { - assert_eq!(NonZero::::from_ascii(b"123"), Ok(NonZero::new(123).unwrap())); +fn test_from_ascii_bytes() { + assert_eq!(NonZero::::from_ascii_bytes(b"123"), Ok(NonZero::new(123).unwrap())); assert_eq!( - NonZero::::from_ascii(b"0").err().map(|e| e.kind().clone()), + NonZero::::from_ascii_bytes(b"0").err().map(|e| e.kind().clone()), Some(IntErrorKind::Zero) ); assert_eq!( - NonZero::::from_ascii(b"-1").err().map(|e| e.kind().clone()), + NonZero::::from_ascii_bytes(b"-1").err().map(|e| e.kind().clone()), Some(IntErrorKind::InvalidDigit) ); assert_eq!( - NonZero::::from_ascii(b"-129").err().map(|e| e.kind().clone()), + NonZero::::from_ascii_bytes(b"-129").err().map(|e| e.kind().clone()), Some(IntErrorKind::NegOverflow) ); assert_eq!( - NonZero::::from_ascii(b"257").err().map(|e| e.kind().clone()), + NonZero::::from_ascii_bytes(b"257").err().map(|e| e.kind().clone()), Some(IntErrorKind::PosOverflow) ); } diff --git a/library/coretests/tests/num/mod.rs b/library/coretests/tests/num/mod.rs index 90666fc0b1ad2..ac65a3b59c66a 100644 --- a/library/coretests/tests/num/mod.rs +++ b/library/coretests/tests/num/mod.rs @@ -118,7 +118,7 @@ fn from_str_issue7588() { #[test] #[should_panic = "radix must lie in the range `[2, 36]`"] -fn from_ascii_radix_panic() { +fn from_ascii_bytes_radix_panic() { let radix = 1; let _parsed = u64::from_str_radix("12345ABCD", radix); } diff --git a/library/std/src/sys/platform_version/darwin/mod.rs b/library/std/src/sys/platform_version/darwin/mod.rs index 06b97fcdef4f2..4756716452c88 100644 --- a/library/std/src/sys/platform_version/darwin/mod.rs +++ b/library/std/src/sys/platform_version/darwin/mod.rs @@ -313,17 +313,17 @@ unsafe fn string_version_key( /// Parse an OS version from a bytestring like b"10.1" or b"14.3.7". fn parse_os_version(version: &[u8]) -> Result { if let Some((major, minor)) = version.split_once(|&b| b == b'.') { - let major = u16::from_ascii(major)?; + let major = u16::from_ascii_bytes(major)?; if let Some((minor, patch)) = minor.split_once(|&b| b == b'.') { - let minor = u8::from_ascii(minor)?; - let patch = u8::from_ascii(patch)?; + let minor = u8::from_ascii_bytes(minor)?; + let patch = u8::from_ascii_bytes(patch)?; Ok(pack_os_version(major, minor, patch)) } else { - let minor = u8::from_ascii(minor)?; + let minor = u8::from_ascii_bytes(minor)?; Ok(pack_os_version(major, minor, 0)) } } else { - let major = u16::from_ascii(version)?; + let major = u16::from_ascii_bytes(version)?; Ok(pack_os_version(major, 0, 0)) } } diff --git a/tests/ui/consts/const-eval/parse_ints.stderr b/tests/ui/consts/const-eval/parse_ints.stderr index 7f42e20cf8e83..b8027fd951d5a 100644 --- a/tests/ui/consts/const-eval/parse_ints.stderr +++ b/tests/ui/consts/const-eval/parse_ints.stderr @@ -1,4 +1,4 @@ -error[E0080]: evaluation panicked: from_ascii_radix: radix must lie in the range `[2, 36]` +error[E0080]: evaluation panicked: from_ascii_bytes_radix: radix must lie in the range `[2, 36]` --> $DIR/parse_ints.rs:7:24 | LL | const _TOO_LOW: () = { u64::from_str_radix("12345ABCD", 1); }; @@ -9,14 +9,14 @@ note: inside `core::num::::from_str_radix` ::: $SRC_DIR/core/src/num/mod.rs:LL:COL | = note: in this macro invocation -note: inside `core::num::::from_ascii_radix` +note: inside `core::num::::from_ascii_bytes_radix_impl` --> $SRC_DIR/core/src/num/mod.rs:LL:COL ::: $SRC_DIR/core/src/num/mod.rs:LL:COL | = note: in this macro invocation = note: this error originates in the macro `from_str_int_impl` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0080]: evaluation panicked: from_ascii_radix: radix must lie in the range `[2, 36]` +error[E0080]: evaluation panicked: from_ascii_bytes_radix: radix must lie in the range `[2, 36]` --> $DIR/parse_ints.rs:8:25 | LL | const _TOO_HIGH: () = { u64::from_str_radix("12345ABCD", 37); }; @@ -27,7 +27,7 @@ note: inside `core::num::::from_str_radix` ::: $SRC_DIR/core/src/num/mod.rs:LL:COL | = note: in this macro invocation -note: inside `core::num::::from_ascii_radix` +note: inside `core::num::::from_ascii_bytes_radix_impl` --> $SRC_DIR/core/src/num/mod.rs:LL:COL ::: $SRC_DIR/core/src/num/mod.rs:LL:COL | From 87c8b4e271f7fe42b65eab7651809a7d63c5f278 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 21 Jul 2026 07:10:47 +1000 Subject: [PATCH 02/28] Don't re-derive the terminator Use the one that's passed in. --- compiler/rustc_mir_dataflow/src/impls/initialized.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/initialized.rs b/compiler/rustc_mir_dataflow/src/impls/initialized.rs index b4cbe7d1da14c..3468ca8d2f51d 100644 --- a/compiler/rustc_mir_dataflow/src/impls/initialized.rs +++ b/compiler/rustc_mir_dataflow/src/impls/initialized.rs @@ -658,10 +658,9 @@ impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> { terminator: &'mir mir::Terminator<'tcx>, location: Location, ) -> TerminatorEdges<'mir, 'tcx> { - let (body, move_data) = (self.body, self.move_data()); - let term = body[location.block].terminator(); + let move_data = self.move_data(); let init_loc_map = &move_data.init_loc_map; - debug!(?term); + debug!(?terminator); debug!("initializes move_indexes {:?}", init_loc_map[location]); state.gen_all( init_loc_map[location] From 205f590bf426d3334acc73ac0530f1055a2bb5d4 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 21 Jul 2026 09:24:10 +1000 Subject: [PATCH 03/28] Clarify `MoveData` - By improving various comments. - By renaming some fields. This gives a clear split between `move_outs`/`move_out_loc_map`/`move_out_path_map` and `inits`/`init_loc_map`/`init_path_map`. --- compiler/rustc_borrowck/src/borrow_set.rs | 2 +- .../src/diagnostics/conflict_errors.rs | 10 +-- compiler/rustc_borrowck/src/lib.rs | 4 +- .../rustc_borrowck/src/polonius/legacy/mod.rs | 6 +- compiler/rustc_borrowck/src/used_muts.rs | 4 +- .../src/drop_flag_effects.rs | 2 +- .../src/move_paths/builder.rs | 28 ++++----- .../rustc_mir_dataflow/src/move_paths/mod.rs | 61 +++++++++++++------ 8 files changed, 71 insertions(+), 46 deletions(-) diff --git a/compiler/rustc_borrowck/src/borrow_set.rs b/compiler/rustc_borrowck/src/borrow_set.rs index c92f94bf3c28c..0260dc333068a 100644 --- a/compiler/rustc_borrowck/src/borrow_set.rs +++ b/compiler/rustc_borrowck/src/borrow_set.rs @@ -217,7 +217,7 @@ impl LocalsStateAtExit { HasStorageDead(DenseBitSet::new_empty(body.local_decls.len())); has_storage_dead.visit_body(body); let mut has_storage_dead_or_moved = has_storage_dead.0; - for move_out in &move_data.moves { + for move_out in &move_data.move_outs { has_storage_dead_or_moved.insert(move_data.base_local(move_out.path)); } LocalsStateAtExit::SomeAreInvalidated { has_storage_dead_or_moved } diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 0e4b29200ff98..9e6510ea33c60 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -129,7 +129,7 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> { } let is_partial_move = move_site_vec.iter().any(|move_site| { - let move_out = self.move_data.moves[(*move_site).moi]; + let move_out = self.move_data.move_outs[(*move_site).moi]; let moved_place = &self.move_data.move_paths[move_out.path].place; // `*(_1)` where `_1` is a `Box` is actually a move out. let is_box_move = moved_place.as_ref().projection == [ProjectionElem::Deref] @@ -215,7 +215,7 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> { let mut seen_spans = FxIndexSet::default(); for move_site in &move_site_vec { - let move_out = self.move_data.moves[(*move_site).moi]; + let move_out = self.move_data.move_outs[(*move_site).moi]; let moved_place = &self.move_data.move_paths[move_out.path].place; let move_spans = self.move_spans(moved_place.as_ref(), move_out.source); @@ -281,7 +281,7 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> { _ => true, }; - let mpi = self.move_data.moves[move_out_indices[0]].path; + let mpi = self.move_data.move_outs[move_out_indices[0]].path; let place = &self.move_data.move_paths[mpi].place; let ty = place.ty(self.body, self.infcx.tcx).ty; @@ -3855,9 +3855,9 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> { // worry about the other case: that is, if there is a move of a.b.c, it is already // marked as a move of a.b and a as well, so we will generate the correct errors // there. - for moi in &self.move_data.loc_map[location] { + for moi in &self.move_data.move_out_loc_map[location] { debug!("report_use_of_moved_or_uninitialized: moi={:?}", moi); - let path = self.move_data.moves[*moi].path; + let path = self.move_data.move_outs[*moi].path; if mpis.contains(&path) { debug!( "report_use_of_moved_or_uninitialized: found {:?}", diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 1e52c9baebae7..96c99e68f95e8 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -2372,8 +2372,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { // of the union - we should error in that case. let tcx = this.infcx.tcx; if base.ty(this.body(), tcx).ty.is_union() - && this.move_data.path_map[mpi].iter().any(|moi| { - this.move_data.moves[*moi].source.is_predecessor_of(location, this.body) + && this.move_data.move_out_path_map[mpi].iter().any(|moi| { + this.move_data.move_outs[*moi].source.is_predecessor_of(location, this.body) }) { return; diff --git a/compiler/rustc_borrowck/src/polonius/legacy/mod.rs b/compiler/rustc_borrowck/src/polonius/legacy/mod.rs index 05fd6e39476b2..0ae3ff3c04790 100644 --- a/compiler/rustc_borrowck/src/polonius/legacy/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/legacy/mod.rs @@ -132,9 +132,9 @@ fn emit_move_facts( // moved_out_at // deinitialisation is assumed to always happen! - facts - .path_moved_at_base - .extend(move_data.moves.iter().map(|mo| (mo.path, location_table.mid_index(mo.source)))); + facts.path_moved_at_base.extend( + move_data.move_outs.iter().map(|mo| (mo.path, location_table.mid_index(mo.source))), + ); } /// Emit universal regions facts, and their relations. diff --git a/compiler/rustc_borrowck/src/used_muts.rs b/compiler/rustc_borrowck/src/used_muts.rs index e743b0ce6e901..16a9962f1190a 100644 --- a/compiler/rustc_borrowck/src/used_muts.rs +++ b/compiler/rustc_borrowck/src/used_muts.rs @@ -93,8 +93,8 @@ impl<'tcx> Visitor<'tcx> for GatherUsedMutsVisitor<'_, '_, '_, 'tcx> { fn visit_local(&mut self, local: Local, place_context: PlaceContext, location: Location) { if place_context.is_place_assignment() && self.temporary_used_locals.contains(&local) { // Propagate the Local assigned at this Location as a used mutable local variable - for moi in &self.mbcx.move_data.loc_map[location] { - let mpi = &self.mbcx.move_data.moves[*moi].path; + for moi in &self.mbcx.move_data.move_out_loc_map[location] { + let mpi = &self.mbcx.move_data.move_outs[*moi].path; let path = &self.mbcx.move_data.move_paths[*mpi]; debug!( "assignment of {:?} to {:?}, adding {:?} to used mutable set", diff --git a/compiler/rustc_mir_dataflow/src/drop_flag_effects.rs b/compiler/rustc_mir_dataflow/src/drop_flag_effects.rs index 1402a1a8b9136..b6fc1219a8503 100644 --- a/compiler/rustc_mir_dataflow/src/drop_flag_effects.rs +++ b/compiler/rustc_mir_dataflow/src/drop_flag_effects.rs @@ -114,7 +114,7 @@ pub fn drop_flag_effects_for_location<'tcx, F>( debug!("drop_flag_effects_for_location({:?})", loc); // first, move out of the RHS - for mi in &move_data.loc_map[loc] { + for mi in &move_data.move_out_loc_map[loc] { let path = mi.move_path_index(move_data); debug!("moving out of path {:?}", move_data.move_paths[path]); diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index c9198da72c750..74aaa19bf2373 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -23,7 +23,7 @@ struct MoveDataBuilder<'a, 'tcx, F> { impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { fn new(body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, filter: F) -> Self { let mut move_paths = IndexVec::new(); - let mut path_map = IndexVec::new(); + let mut move_out_path_map = IndexVec::new(); let mut init_path_map = IndexVec::new(); let locals = body @@ -36,7 +36,7 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { if filter(l.ty) { Some(new_move_path( &mut move_paths, - &mut path_map, + &mut move_out_path_map, &mut init_path_map, None, Place::from(i), @@ -52,15 +52,15 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { loc: Location::START, tcx, data: MoveData { - moves: IndexVec::new(), - loc_map: LocationMap::new(body), + move_outs: IndexVec::new(), + move_out_loc_map: LocationMap::new(body), rev_lookup: MovePathLookup { locals, projections: Default::default(), un_derefer: Default::default(), }, move_paths, - path_map, + move_out_path_map, inits: IndexVec::new(), init_loc_map: LocationMap::new(body), init_path_map, @@ -72,7 +72,7 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { fn new_move_path<'tcx>( move_paths: &mut IndexVec>, - path_map: &mut IndexVec>, + move_out_path_map: &mut IndexVec>, init_path_map: &mut IndexVec>, parent: Option, place: Place<'tcx>, @@ -85,7 +85,7 @@ fn new_move_path<'tcx>( move_paths[move_path].next_sibling = next_sibling; } - let path_map_ent = path_map.push(smallvec![]); + let path_map_ent = move_out_path_map.push(smallvec![]); assert_eq!(path_map_ent, move_path); let init_path_map_ent = init_path_map.push(smallvec![]); @@ -279,7 +279,7 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { base = *data.rev_lookup.projections.entry((base, move_elem)).or_insert_with(|| { new_move_path( &mut data.move_paths, - &mut data.path_map, + &mut data.move_out_path_map, &mut data.init_path_map, Some(base), place_ref.project_deeper(&[elem], tcx), @@ -305,12 +305,12 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { mk_place: impl FnOnce(TyCtxt<'tcx>) -> Place<'tcx>, ) -> MovePathIndex { let MoveDataBuilder { - data: MoveData { rev_lookup, move_paths, path_map, init_path_map, .. }, + data: MoveData { rev_lookup, move_paths, move_out_path_map, init_path_map, .. }, tcx, .. } = self; *rev_lookup.projections.entry((base, elem)).or_insert_with(move || { - new_move_path(move_paths, path_map, init_path_map, Some(base), mk_place(*tcx)) + new_move_path(move_paths, move_out_path_map, init_path_map, Some(base), mk_place(*tcx)) }) } @@ -323,7 +323,7 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { fn finalize(self) -> MoveData<'tcx> { debug!("{}", { debug!("moves for {:?}:", self.body.span); - for (j, mo) in self.data.moves.iter_enumerated() { + for (j, mo) in self.data.move_outs.iter_enumerated() { debug!(" {:?} = {:?}", j, mo); } debug!("move paths for {:?}:", self.body.span); @@ -553,13 +553,13 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { } fn record_move(&mut self, place: Place<'tcx>, path: MovePathIndex) { - let move_out = self.data.moves.push(MoveOut { path, source: self.loc }); + let move_out = self.data.move_outs.push(MoveOut { path, source: self.loc }); debug!( "gather_move({:?}, {:?}): adding move {:?} of {:?}", self.loc, place, move_out, path ); - self.data.path_map[path].push(move_out); - self.data.loc_map[self.loc].push(move_out); + self.data.move_out_path_map[path].push(move_out); + self.data.move_out_loc_map[self.loc].push(move_out); } fn gather_init(&mut self, place: PlaceRef<'tcx>, kind: InitKind) { diff --git a/compiler/rustc_mir_dataflow/src/move_paths/mod.rs b/compiler/rustc_mir_dataflow/src/move_paths/mod.rs index 7f8872b3e3493..f53137948f99c 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/mod.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/mod.rs @@ -14,6 +14,7 @@ use smallvec::SmallVec; use crate::un_derefer::UnDerefer; rustc_index::newtype_index! { + /// Index identifying a `MovePath`. #[orderable] #[debug_format = "mp{}"] pub struct MovePathIndex {} @@ -26,26 +27,31 @@ impl polonius_engine::Atom for MovePathIndex { } rustc_index::newtype_index! { + /// Index identifying a `MoveOut`. #[orderable] #[debug_format = "mo{}"] pub struct MoveOutIndex {} } rustc_index::newtype_index! { + /// Index identifying an `Init`. #[debug_format = "in{}"] pub struct InitIndex {} } impl MoveOutIndex { pub fn move_path_index(self, move_data: &MoveData<'_>) -> MovePathIndex { - move_data.moves[self].path + move_data.move_outs[self].path } } -/// `MovePath` is a canonicalized representation of a path that is -/// moved or assigned to. +/// `MovePath` is a canonicalized representation of a place that is of +/// interest to dataflow analysis, as identified by `gather_moves`. This +/// is primarily places that are moved or inited (assigned). Each +/// `MovePath` is assigned a `MovePathIndex` by which it can be referred +/// to. /// -/// It follows a tree structure. +/// `MovePath` follows a tree structure. /// /// Given `struct X { m: M, n: N }` and `x: X`, moves like `drop x.m;` /// move *out* of the place `x.m`. @@ -54,6 +60,8 @@ impl MoveOutIndex { /// one of them will link to the other via the `next_sibling` field, /// and the other will have no entry in its `next_sibling` field), and /// they both have the MovePath representing `x` as their parent. +/// (All tree roots are locals). This structure allows easy traversal +/// between related paths `x` and `x.m` and `x.n`. #[derive(Clone)] pub struct MovePath<'tcx> { pub next_sibling: Option, @@ -166,20 +174,28 @@ where #[derive(Debug)] pub struct MoveData<'tcx> { + /// All the gathered `MovePath`s. pub move_paths: IndexVec>, - pub moves: IndexVec, - /// Each Location `l` is mapped to the MoveOut's that are effects - /// of executing the code at `l`. (There can be multiple MoveOut's - /// for a given `l` because each MoveOut is associated with one - /// particular path being moved.) - pub loc_map: LocationMap>, - pub path_map: IndexVec>, + + /// All the `MoveOut`s. + pub move_outs: IndexVec, + /// Map from locations to `MoveOut`s. `SmallVec` because each location might cause more than + /// one `MoveOut`. Used during analysis and diagnostics. + pub move_out_loc_map: LocationMap>, + /// Map from `MovePath`s (places) to `MoveOuts`. `SmallVec` because each `MovePath` may be + /// moved-out of more than once. Used mostly for diagnostics. + pub move_out_path_map: IndexVec>, + + /// Map from places/locals to `MovePath`s. pub rev_lookup: MovePathLookup<'tcx>, + + /// All the `Init`s. pub inits: IndexVec, - /// Each Location `l` is mapped to the Inits that are effects - /// of executing the code at `l`. Only very rarely (e.g. inline asm) - /// is there more than one Init at any `l`. + /// Map from locations to `Init`s. `SmallVec` because each location might cause more than one + /// `Init`, though more than one is very rare (e.g. inline asm). pub init_loc_map: LocationMap>, + /// Map from `MovePath`s (places) to `Init`s. `SmallVec` because each `MovePath` (place) might + /// be inited more than once. pub init_path_map: IndexVec>, } @@ -223,7 +239,7 @@ where } /// `MoveOut` represents a point in a program that moves out of some -/// L-value; i.e., "creates" uninitialized memory. +/// L-value; i.e., "creates" uninitialized memory. The dual of `Init`. /// /// With respect to dataflow analysis: /// - Generated by moves and declaration of uninitialized variables. @@ -242,7 +258,7 @@ impl fmt::Debug for MoveOut { } } -/// `Init` represents a point in a program that initializes some L-value; +/// `Init` represents a point in a program that initializes some L-value. The dual of `MoveOut`. #[derive(Copy, Clone)] pub struct Init { /// path being initialized @@ -254,7 +270,7 @@ pub struct Init { } /// Initializations can be from an argument or from a statement. Arguments -/// do not have locations, in those cases the `Local` is kept.. +/// do not have locations, in those cases the `Local` is kept. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum InitLocation { Argument(Local), @@ -287,7 +303,7 @@ impl Init { } } -/// Tables mapping from a place to its MovePathIndex. +/// Tables mapping from a place to its `MovePathIndex`. #[derive(Debug)] pub struct MovePathLookup<'tcx> { locals: IndexVec>, @@ -307,7 +323,13 @@ mod builder; #[derive(Copy, Clone, Debug)] pub enum LookupResult { + /// This exact thing has a move path. E.g. we looked up `x` or `x.m` and it has been moved. Exact(MovePathIndex), + + /// - If the field is `None`, neither the exact thing nor any ancestor of it has a move path. + /// E.g. we looked up `x.m` and neither it nor `x` have a move path. + /// - If the field is `Some`, the exact thing has no move path, but an ancestor does. E.g. we + /// looked up `x.m` which has no move path but `x` has one. Not possible for locals. Parent(Option), } @@ -317,10 +339,12 @@ impl<'tcx> MovePathLookup<'tcx> { // unknown place, but will rather return the nearest available // parent. pub fn find(&self, place: PlaceRef<'tcx>) -> LookupResult { + // Look first in the locals (roots). let Some(mut result) = self.find_local(place.local) else { return LookupResult::Parent(None); }; + // Look for a projection through the found local. for (_, elem) in self.un_derefer.iter_projections(place) { let subpath = match MoveSubPath::of(elem.kind()) { MoveSubPathResult::One(kind) => self.projections.get(&(result, kind)), @@ -338,6 +362,7 @@ impl<'tcx> MovePathLookup<'tcx> { LookupResult::Exact(result) } + /// For locals, which are roots. #[inline] pub fn find_local(&self, local: Local) -> Option { self.locals[local] From 06a0c50a7f4b91e90f108ac2f306a94b121241e9 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 21 Jul 2026 14:21:47 +1000 Subject: [PATCH 04/28] Simplify a discriminants iteration By using an iterator instead of a manual loop. --- .../src/impls/initialized.rs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/initialized.rs b/compiler/rustc_mir_dataflow/src/impls/initialized.rs index 3468ca8d2f51d..180cc16774abb 100644 --- a/compiler/rustc_mir_dataflow/src/impls/initialized.rs +++ b/compiler/rustc_mir_dataflow/src/impls/initialized.rs @@ -59,25 +59,21 @@ impl<'tcx> MaybePlacesSwitchIntData<'tcx> { { match enum_place.ty(body, tcx).ty.kind() { ty::Adt(enum_def, _) => { - // The value of each discriminant, in AdtDef order. - let discriminant_vals: SmallVec<[u128; 4]> = - enum_def.discriminants(tcx).map(|(_, discr)| discr.val).collect(); - let mut i = 0; - // For each value in the SwitchInt, find the VariantIdx for the variant // with that value. This works because `discriminant_vals` and // `targets.all_values()` are guaranteed to list variants in the same - // order. (If that ever changes we will get out-of-bounds panics here.) + // AdtDef order. (If that ever changes the `expect` will panic.) + let mut discriminants = enum_def.discriminants(tcx); let variants = targets .all_values() .iter() .map(|value| { - loop { - if discriminant_vals[i] == value.get() { - return VariantIdx::new(i); - } - i += 1; - } + // On each call to this closure `find` only consumes part of + // the `discriminants` iterator. + discriminants + .find(|(_, discr)| discr.val == value.get()) + .expect("SwitchInt vals should match a variant") + .0 }) .collect(); From 3fb482fdde2d0df0853eca1cc0e55387c7722798 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 21 Jul 2026 14:26:54 +1000 Subject: [PATCH 05/28] Remove `MaybeUninitializedPlaces::include_inactive_in_otherwise` It always has the same value as `mark_inactive_variants_as_uninit`, and it's only read if `mark_inactive_variants_as_uninit` is true. --- .../rustc_mir_dataflow/src/impls/initialized.rs | 14 +------------- .../rustc_mir_transform/src/elaborate_drops.rs | 1 - 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/initialized.rs b/compiler/rustc_mir_dataflow/src/impls/initialized.rs index 180cc16774abb..4ca5ee56315a4 100644 --- a/compiler/rustc_mir_dataflow/src/impls/initialized.rs +++ b/compiler/rustc_mir_dataflow/src/impls/initialized.rs @@ -228,7 +228,6 @@ pub struct MaybeUninitializedPlaces<'a, 'tcx> { move_data: &'a MoveData<'tcx>, mark_inactive_variants_as_uninit: bool, - include_inactive_in_otherwise: bool, skip_unreachable_unwind: DenseBitSet, } @@ -239,7 +238,6 @@ impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> { body, move_data, mark_inactive_variants_as_uninit: false, - include_inactive_in_otherwise: false, skip_unreachable_unwind: DenseBitSet::new_empty(body.basic_blocks.len()), } } @@ -254,13 +252,6 @@ impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> { self } - /// Ensures definitely inactive variants are included in the set of uninitialized places for - /// blocks reached through an `otherwise` edge. - pub fn include_inactive_in_otherwise(mut self) -> Self { - self.include_inactive_in_otherwise = true; - self - } - pub fn skipping_unreachable_unwind( mut self, unreachable_unwind: DenseBitSet, @@ -585,10 +576,7 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { SwitchTargetIndex::Normal(target_idx) => { InactiveVariants::Active(data.variants[target_idx]) } - SwitchTargetIndex::Otherwise if self.include_inactive_in_otherwise => { - InactiveVariants::Inactives(data.variants.clone()) - } - _ => return, + SwitchTargetIndex::Otherwise => InactiveVariants::Inactives(data.variants.clone()), }; // Mark all move paths that correspond to variants other than this one as maybe diff --git a/compiler/rustc_mir_transform/src/elaborate_drops.rs b/compiler/rustc_mir_transform/src/elaborate_drops.rs index 01235b1824218..1fc3f55f8e145 100644 --- a/compiler/rustc_mir_transform/src/elaborate_drops.rs +++ b/compiler/rustc_mir_transform/src/elaborate_drops.rs @@ -68,7 +68,6 @@ impl<'tcx> crate::MirPass<'tcx> for ElaborateDrops { let dead_unwinds = compute_dead_unwinds(body, &mut inits); let uninits = MaybeUninitializedPlaces::new(tcx, body, &env.move_data) - .include_inactive_in_otherwise() .mark_inactive_variants_as_uninit() .skipping_unreachable_unwind(dead_unwinds) .iterate_to_fixpoint(tcx, body, Some("elaborate_drops")) From 1fb0965feeeeb10a5d4abcf5898d4a2b86cf4a51 Mon Sep 17 00:00:00 2001 From: Adwin White Date: Wed, 22 Jul 2026 18:20:49 +0800 Subject: [PATCH 06/28] move `eager_resolve_vars` to `rustc_type_ir` --- .../src/canonical/mod.rs | 3 +- compiler/rustc_next_trait_solver/src/lib.rs | 1 - .../rustc_next_trait_solver/src/normalize.rs | 10 +- .../rustc_next_trait_solver/src/resolve.rs | 107 ------------------ .../src/solve/eval_ctxt/mod.rs | 3 +- .../src/solve/inspect/analyse.rs | 3 +- compiler/rustc_type_ir/src/infer_ctxt.rs | 105 ++++++++++++++++- 7 files changed, 111 insertions(+), 121 deletions(-) delete mode 100644 compiler/rustc_next_trait_solver/src/resolve.rs diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index f325862102318..5d54065260888 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -19,12 +19,11 @@ use rustc_type_ir::relate::{ }; use rustc_type_ir::{ self as ty, Canonical, CanonicalVarKind, CanonicalVarValues, InferCtxtLike, Interner, Region, - TypeFoldable, TypingMode, TypingModeEqWrapper, + TypeFoldable, TypingMode, TypingModeEqWrapper, eager_resolve_vars, }; use tracing::instrument; use crate::delegate::SolverDelegate; -use crate::resolve::eager_resolve_vars; use crate::solve::{ CanonicalInput, CanonicalResponse, Certainty, ExternalConstraintsData, ExternalRegionConstraints, Goal, NestedNormalizationGoals, QueryInput, Response, diff --git a/compiler/rustc_next_trait_solver/src/lib.rs b/compiler/rustc_next_trait_solver/src/lib.rs index 57bbf8772d322..6396c0e7f2b57 100644 --- a/compiler/rustc_next_trait_solver/src/lib.rs +++ b/compiler/rustc_next_trait_solver/src/lib.rs @@ -15,5 +15,4 @@ pub mod coherence; pub mod delegate; pub mod normalize; pub mod placeholder; -pub mod resolve; pub mod solve; diff --git a/compiler/rustc_next_trait_solver/src/normalize.rs b/compiler/rustc_next_trait_solver/src/normalize.rs index 959e18071bb57..e2036f0ab0cc3 100644 --- a/compiler/rustc_next_trait_solver/src/normalize.rs +++ b/compiler/rustc_next_trait_solver/src/normalize.rs @@ -4,7 +4,7 @@ use rustc_type_ir::data_structures::ensure_sufficient_stack; use rustc_type_ir::inherent::*; use rustc_type_ir::{ self as ty, AliasTerm, Binder, FallibleTypeFolder, InferCtxtLike, Interner, TypeFoldable, - TypeSuperFoldable, TypeVisitableExt, UniverseIndex, + TypeSuperFoldable, TypeVisitableExt, UniverseIndex, eager_resolve_vars, }; use tracing::instrument; @@ -143,8 +143,8 @@ where if self.cx().renormalize_rigid_aliases() && orig_is_rigid == ty::IsRigid::Yes { // find out missing typing env change. - let original = crate::resolve::eager_resolve_vars(infcx, original); - let normalized = crate::resolve::eager_resolve_vars(infcx, normalized); + let original = eager_resolve_vars(infcx, original); + let normalized = eager_resolve_vars(infcx, normalized); assert_eq!(original, normalized, "rigid alias is further normalized"); } Ok(normalized) @@ -196,8 +196,8 @@ where if self.cx().renormalize_rigid_aliases() && orig_is_rigid == ty::IsRigid::Yes { // find out missing typing env change. - let original = crate::resolve::eager_resolve_vars(infcx, original); - let normalized = crate::resolve::eager_resolve_vars(infcx, normalized); + let original = eager_resolve_vars(infcx, original); + let normalized = eager_resolve_vars(infcx, normalized); assert_eq!(original, normalized, "rigid alias is further normalized"); } diff --git a/compiler/rustc_next_trait_solver/src/resolve.rs b/compiler/rustc_next_trait_solver/src/resolve.rs deleted file mode 100644 index 3a927e2a92506..0000000000000 --- a/compiler/rustc_next_trait_solver/src/resolve.rs +++ /dev/null @@ -1,107 +0,0 @@ -use rustc_type_ir::data_structures::DelayedMap; -use rustc_type_ir::inherent::*; -use rustc_type_ir::{ - self as ty, InferCtxtLike, Interner, Region, TypeFoldable, TypeFolder, TypeSuperFoldable, - TypeVisitableExt, -}; - -/////////////////////////////////////////////////////////////////////////// -// EAGER RESOLUTION - -/// Resolves ty, region, and const vars to their inferred values or their root vars. -struct EagerResolver<'a, D, I = ::Interner> -where - D: InferCtxtLike, - I: Interner, -{ - delegate: &'a D, - /// We're able to use a cache here as the folder does not have any - /// mutable state. - cache: DelayedMap, -} - -pub fn eager_resolve_vars>( - infcx: &Infcx, - value: T, -) -> T { - if value.has_infer() { - let mut folder = EagerResolver::new(infcx); - value.fold_with(&mut folder) - } else { - value - } -} - -impl<'a, Infcx: InferCtxtLike> EagerResolver<'a, Infcx> { - fn new(delegate: &'a Infcx) -> Self { - EagerResolver { delegate, cache: Default::default() } - } -} - -impl, I: Interner> TypeFolder for EagerResolver<'_, Infcx> { - fn cx(&self) -> I { - self.delegate.cx() - } - - fn fold_ty(&mut self, t: I::Ty) -> I::Ty { - match t.kind() { - ty::Infer(ty::TyVar(vid)) => { - let resolved = self.delegate.opportunistic_resolve_ty_var(vid); - if t != resolved && resolved.has_infer() { - resolved.fold_with(self) - } else { - resolved - } - } - ty::Infer(ty::IntVar(vid)) => self.delegate.opportunistic_resolve_int_var(vid), - ty::Infer(ty::FloatVar(vid)) => self.delegate.opportunistic_resolve_float_var(vid), - _ => { - if t.has_infer() { - if let Some(&ty) = self.cache.get(&t) { - return ty; - } - let res = t.super_fold_with(self); - assert!(self.cache.insert(t, res)); - res - } else { - t - } - } - } - } - - fn fold_region(&mut self, r: Region) -> Region { - match r.kind() { - ty::ReVar(vid) => self.delegate.opportunistic_resolve_lt_var(vid), - _ => r, - } - } - - fn fold_const(&mut self, c: I::Const) -> I::Const { - match c.kind() { - ty::ConstKind::Infer(ty::InferConst::Var(vid)) => { - let resolved = self.delegate.opportunistic_resolve_ct_var(vid); - if c != resolved && resolved.has_infer() { - resolved.fold_with(self) - } else { - resolved - } - } - _ => { - if c.has_infer() { - c.super_fold_with(self) - } else { - c - } - } - } - } - - fn fold_predicate(&mut self, p: I::Predicate) -> I::Predicate { - if p.has_infer() { p.super_fold_with(self) } else { p } - } - - fn fold_clauses(&mut self, c: I::Clauses) -> I::Clauses { - if c.has_infer() { c.super_fold_with(self) } else { c } - } -} diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index c3ccb46069063..cfb3b6b85f7cc 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -17,7 +17,7 @@ use rustc_type_ir::solve::{ use rustc_type_ir::{ self as ty, CanonicalVarValues, ClauseKind, InferCtxtLike, Interner, MayBeErased, OpaqueTypeKey, PredicateKind, Region, TypeFoldable, TypeSuperVisitable, TypeVisitable, - TypeVisitableExt, TypeVisitor, TypingMode, + TypeVisitableExt, TypeVisitor, TypingMode, eager_resolve_vars, }; use tracing::{Level, debug, instrument, trace, warn}; @@ -30,7 +30,6 @@ use crate::coherence; use crate::delegate::SolverDelegate; use crate::normalize::{NormalizationFolder, NormalizationWasAmbiguous}; use crate::placeholder::BoundVarReplacer; -use crate::resolve::eager_resolve_vars; use crate::solve::eval_ctxt::fast_path::{ RerunStalled, compute_goal_fast_path, rerunning_stalled_goal_may_make_progress, }; diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs index 7bf41d343ecf4..37cbdfb66f505 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs @@ -14,10 +14,9 @@ use std::assert_matches; use rustc_infer::infer::InferCtxt; use rustc_macros::extension; use rustc_middle::traits::solve::{Certainty, Goal, GoalSource, NoSolution, QueryResult}; -use rustc_middle::ty::{TyCtxt, VisitorResult, try_visit}; +use rustc_middle::ty::{TyCtxt, VisitorResult, eager_resolve_vars, try_visit}; use rustc_middle::{bug, ty}; use rustc_next_trait_solver::canonical::instantiate_canonical_state; -use rustc_next_trait_solver::resolve::eager_resolve_vars; use rustc_next_trait_solver::solve::{MaybeCause, MaybeInfo, SolverDelegateEvalExt as _, inspect}; use rustc_span::Span; use tracing::instrument; diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index 75f906e498eda..2f37ab94c152b 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -5,12 +5,15 @@ use derive_where::derive_where; #[cfg(feature = "nightly")] use rustc_macros::{Decodable_NoContext, Encodable_NoContext, StableHash_NoContext}; -use crate::fold::TypeFoldable; +use crate::data_structures::DelayedMap; use crate::inherent::*; use crate::relate::RelateResult; use crate::relate::combine::PredicateEmittingRelation; use crate::solve::VisibleForLeakCheck; -use crate::{self as ty, Interner, Region, TyVid}; +use crate::{ + self as ty, Interner, Region, TyVid, TypeFoldable, TypeFolder, TypeSuperFoldable, + TypeVisitableExt, +}; mod private { pub trait Sealed {} @@ -568,3 +571,101 @@ where TypingMode::Codegen => true, } } + +/// Resolves ty, region, and const vars to their inferred values or their root vars. +pub fn eager_resolve_vars>( + infcx: &Infcx, + value: T, +) -> T { + if value.has_infer() { + let mut folder = EagerResolver::new(infcx); + value.fold_with(&mut folder) + } else { + value + } +} + +struct EagerResolver<'a, D, I = ::Interner> +where + D: InferCtxtLike, + I: Interner, +{ + delegate: &'a D, + /// We're able to use a cache here as the folder does not have any + /// mutable state. + cache: DelayedMap, +} + +impl<'a, Infcx: InferCtxtLike> EagerResolver<'a, Infcx> { + fn new(delegate: &'a Infcx) -> Self { + EagerResolver { delegate, cache: Default::default() } + } +} + +impl, I: Interner> TypeFolder for EagerResolver<'_, Infcx> { + fn cx(&self) -> I { + self.delegate.cx() + } + + fn fold_ty(&mut self, t: I::Ty) -> I::Ty { + match t.kind() { + ty::Infer(ty::TyVar(vid)) => { + let resolved = self.delegate.opportunistic_resolve_ty_var(vid); + if t != resolved && resolved.has_infer() { + resolved.fold_with(self) + } else { + resolved + } + } + ty::Infer(ty::IntVar(vid)) => self.delegate.opportunistic_resolve_int_var(vid), + ty::Infer(ty::FloatVar(vid)) => self.delegate.opportunistic_resolve_float_var(vid), + _ => { + if t.has_infer() { + if let Some(&ty) = self.cache.get(&t) { + return ty; + } + let res = t.super_fold_with(self); + assert!(self.cache.insert(t, res)); + res + } else { + t + } + } + } + } + + fn fold_region(&mut self, r: Region) -> Region { + match r.kind() { + ty::ReVar(vid) => self.delegate.opportunistic_resolve_lt_var(vid), + _ => r, + } + } + + fn fold_const(&mut self, c: I::Const) -> I::Const { + match c.kind() { + ty::ConstKind::Infer(ty::InferConst::Var(vid)) => { + let resolved = self.delegate.opportunistic_resolve_ct_var(vid); + if c != resolved && resolved.has_infer() { + resolved.fold_with(self) + } else { + resolved + } + } + _ => { + if c.has_infer() { + c.super_fold_with(self) + } else { + c + } + } + } + } + + fn fold_predicate(&mut self, p: I::Predicate) -> I::Predicate { + if p.has_infer() { p.super_fold_with(self) } else { p } + } + + fn fold_clauses(&mut self, c: I::Clauses) -> I::Clauses { + if c.has_infer() { c.super_fold_with(self) } else { c } + } +} From f5b6af2ee7bad7439e91069a7f4c0720af564db2 Mon Sep 17 00:00:00 2001 From: Adwin White Date: Mon, 20 Jul 2026 16:32:06 +0800 Subject: [PATCH 07/28] normalize before regionck --- .../src/type_check/constraint_conversion.rs | 129 +++++------------- .../rustc_hir_analysis/src/check/wfcheck.rs | 2 +- .../rustc_infer/src/infer/outlives/mod.rs | 22 +-- .../src/infer/outlives/obligations.rs | 26 +--- .../rustc_next_trait_solver/src/solve/mod.rs | 15 +- compiler/rustc_trait_selection/src/regions.rs | 59 ++------ .../src/solve/delegate.rs | 47 ++++++- .../src/traits/auto_trait.rs | 3 +- .../cycles/cycle-modulo-ambig-aliases.rs | 1 + .../cycles/cycle-modulo-ambig-aliases.stderr | 11 +- 10 files changed, 122 insertions(+), 193 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs index d1e2f2437ae2f..e0e5252ef1ed1 100644 --- a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs +++ b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs @@ -5,14 +5,11 @@ use rustc_infer::infer::canonical::{QueryRegionConstraint, QueryRegionConstraint use rustc_infer::infer::outlives::env::RegionBoundPairs; use rustc_infer::infer::outlives::obligations::{TypeOutlives, TypeOutlivesDelegate}; use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound}; -use rustc_infer::traits::query::type_op::Normalize; -use rustc_middle::bug; use rustc_middle::ty::{ - self, GenericArgKind, RegionExt, RegionUtilitiesExt, Ty, TyCtxt, TypeFoldable, - TypeVisitableExt, elaborate, fold_regions, + self, GenericArgKind, RegionExt, RegionUtilitiesExt, TyCtxt, TypeFoldable, TypeVisitableExt, + elaborate, fold_regions, }; use rustc_span::Span; -use rustc_trait_selection::traits::query::type_op::TypeOpOutput; use tracing::{debug, instrument}; use crate::constraints::OutlivesConstraint; @@ -137,83 +134,49 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { // Extract out various useful fields we'll need below. let ConstraintConversion { - infcx, + infcx: _, universal_regions, region_bound_pairs, known_type_outlives_obligations, .. } = *self; - let mut outlives_predicates = vec![(predicate, constraint_category)]; - for iteration in 0.. { - if outlives_predicates.is_empty() { - break; - } + let pred = predicate; + // Constraint is implied by a coroutine's well-formedness. + if self.infcx.tcx.sess.opts.unstable_opts.higher_ranked_assumptions + && higher_ranked_assumptions.contains(&pred) + { + return; + } - if !tcx.recursion_limit().value_within_limit(iteration) { - // This may actually be reachable. If so, we should convert - // this to a proper error/consider whether we should detect - // this somewhere else. - bug!( - "unexpected overflowed when processing region obligations: {outlives_predicates:#?}" - ); + let ty::OutlivesPredicate(k1, r2) = pred; + match k1.kind() { + GenericArgKind::Lifetime(r1) => { + let r1_vid = self.to_region_vid(r1); + let r2_vid = self.to_region_vid(r2); + self.add_outlives(r1_vid, r2_vid, constraint_category); } - let mut next_outlives_predicates = vec![]; - for (pred, constraint_category) in outlives_predicates { - // Constraint is implied by a coroutine's well-formedness. - if self.infcx.tcx.sess.opts.unstable_opts.higher_ranked_assumptions - && higher_ranked_assumptions.contains(&pred) - { - continue; - } - - let ty::OutlivesPredicate(k1, r2) = pred; - match k1.kind() { - GenericArgKind::Lifetime(r1) => { - let r1_vid = self.to_region_vid(r1); - let r2_vid = self.to_region_vid(r2); - self.add_outlives(r1_vid, r2_vid, constraint_category); - } - - GenericArgKind::Type(mut t1) => { - // Scraped constraints may have had inference vars. - t1 = self.infcx.resolve_vars_if_possible(t1); - - // Normalize the type we receive from a `TypeOutlives` obligation - // in the new trait solver. - if infcx.next_trait_solver() { - t1 = self.normalize_and_add_type_outlives_constraints( - ty::Unnormalized::new_wip(t1), - &mut next_outlives_predicates, - ); - } + GenericArgKind::Type(mut t1) => { + // Scraped constraints may have had inference vars. + t1 = self.infcx.resolve_vars_if_possible(t1); - let implicit_region_bound = - ty::Region::new_var(tcx, universal_regions.implicit_region_bound()); - // we don't actually use this for anything, but - // the `TypeOutlives` code needs an origin. - let origin = SubregionOrigin::RelateParamBound(self.span, t1, None); - TypeOutlives::new( - &mut *self, - tcx, - region_bound_pairs, - Some(implicit_region_bound), - known_type_outlives_obligations, - ) - .type_must_outlive( - origin, - t1, - r2, - constraint_category, - ); - } - - GenericArgKind::Const(_) => unreachable!(), - } + let implicit_region_bound = + ty::Region::new_var(tcx, universal_regions.implicit_region_bound()); + // we don't actually use this for anything, but + // the `TypeOutlives` code needs an origin. + let origin = SubregionOrigin::RelateParamBound(self.span, t1, None); + TypeOutlives::new( + &mut *self, + tcx, + region_bound_pairs, + Some(implicit_region_bound), + known_type_outlives_obligations, + ) + .type_must_outlive(origin, t1, r2, constraint_category); } - outlives_predicates = next_outlives_predicates; + GenericArgKind::Const(_) => unreachable!(), } } @@ -279,32 +242,6 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { debug!("add_type_test(type_test={:?})", type_test); self.constraints.type_tests.push(type_test); } - - // FIXME(trait-refactor-initiative#260): This function should be - // removed. - fn normalize_and_add_type_outlives_constraints( - &self, - ty: ty::Unnormalized<'tcx, Ty<'tcx>>, - next_outlives_predicates: &mut Vec<( - ty::ArgOutlivesPredicate<'tcx>, - ConstraintCategory<'tcx>, - )>, - ) -> Ty<'tcx> { - match self.infcx.fully_perform(Normalize { value: ty }, self.span) { - Ok(TypeOpOutput { output: ty, constraints, .. }) => { - // FIXME(higher_ranked_auto): What should we do with the assumptions here? - if let Some(QueryRegionConstraints { constraints, assumptions: _ }) = constraints { - next_outlives_predicates.extend(constraints.iter().flat_map( - |QueryRegionConstraint { constraint, category, .. }| { - constraint.iter_outlives().map(|outlives| (outlives, *category)) - }, - )); - } - ty - } - Err(_) => ty.skip_norm_wip(), - } - } } impl<'a, 'b, 'tcx> TypeOutlivesDelegate<'tcx> for &'a mut ConstraintConversion<'b, 'tcx> { diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index f59345cd970eb..fd2944a122f03 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -31,7 +31,7 @@ use rustc_session::diagnostics::feature_err; use rustc_span::{DUMMY_SP, Span, sym}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::regions::{ - InferCtxtRegionExt, OutlivesEnvironmentBuildExt, region_known_to_outlive, ty_known_to_outlive, + OutlivesEnvironmentBuildExt, region_known_to_outlive, ty_known_to_outlive, }; use rustc_trait_selection::traits::misc::{ ConstParamTyImplementationError, type_allowed_to_implement_const_param_ty, diff --git a/compiler/rustc_infer/src/infer/outlives/mod.rs b/compiler/rustc_infer/src/infer/outlives/mod.rs index 76db3830d3962..ce2650dff9f18 100644 --- a/compiler/rustc_infer/src/infer/outlives/mod.rs +++ b/compiler/rustc_infer/src/infer/outlives/mod.rs @@ -3,14 +3,14 @@ use std::iter; use rustc_data_structures::undo_log::UndoLogs; -use rustc_middle::traits::query::{NoSolution, OutlivesBound}; +use rustc_middle::traits::query::OutlivesBound; use rustc_middle::ty; use rustc_span::Span; use tracing::instrument; use self::env::OutlivesEnvironment; use super::region_constraints::{RegionConstraintData, UndoLog}; -use super::{InferCtxt, RegionResolutionError, SubregionOrigin}; +use super::{InferCtxt, RegionResolutionError}; use crate::infer::free_regions::RegionRelations; use crate::infer::lexical_region_resolve; use crate::infer::region_constraints::ConstraintKind; @@ -38,25 +38,15 @@ impl<'tcx> InferCtxt<'tcx> { /// done -- or the compiler will panic -- but it is legal to use /// `resolve_vars_if_possible` as well as `fully_resolve`. /// - /// If you are in a crate that has access to `rustc_trait_selection`, - /// then it's probably better to use `resolve_regions`, - /// which knows how to normalize registered region obligations. + /// Don't call this directly unless you know what you're doing. + /// You probably want to use `resolve_regions` instead. #[must_use] - pub fn resolve_regions_with_normalize( + pub fn resolve_regions_with_outlives_env( &self, outlives_env: &OutlivesEnvironment<'tcx>, - deeply_normalize_ty: impl Fn( - ty::PolyTypeOutlivesPredicate<'tcx>, - SubregionOrigin<'tcx>, - ) -> Result, NoSolution>, span: Span, ) -> Vec> { - match self.process_registered_region_obligations(outlives_env, deeply_normalize_ty, span) { - Ok(()) => {} - Err((clause, origin)) => { - return vec![RegionResolutionError::CannotNormalize(clause, origin)]; - } - }; + self.process_registered_region_obligations(outlives_env, span); let mut storage = { let mut inner = self.inner.borrow_mut(); diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index c8a94d26133ea..c2753d7499556 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -63,11 +63,10 @@ use rustc_data_structures::transitive_relation::TransitiveRelation; use rustc_data_structures::undo_log::UndoLogs; use rustc_middle::bug; use rustc_middle::mir::ConstraintCategory; -use rustc_middle::traits::query::NoSolution; use rustc_middle::ty::outlives::{Component, push_outlives_components}; use rustc_middle::ty::{ self, GenericArgKind, GenericArgsRef, PolyTypeOutlivesPredicate, Region, RegionExt, RegionVid, - Ty, TyCtxt, TypeFoldable as _, TypeVisitableExt, + Ty, TyCtxt, TypeVisitableExt, eager_resolve_vars, }; use rustc_span::Span; use smallvec::smallvec; @@ -76,7 +75,6 @@ use tracing::{debug, instrument}; use super::env::OutlivesEnvironment; use crate::infer::outlives::env::RegionBoundPairs; use crate::infer::outlives::verify::VerifyBoundCx; -use crate::infer::resolve::OpportunisticRegionResolver; use crate::infer::snapshot::undo_log::UndoLog; use crate::infer::{ self, GenericKind, InferCtxt, SubregionOrigin, TypeOutlivesConstraint, VerifyBound, @@ -288,17 +286,12 @@ impl<'tcx> InferCtxt<'tcx> { /// flow of the inferencer. The key point is that it is /// invoked after all type-inference variables have been bound -- /// right before lexical region resolution. - #[instrument(level = "debug", skip(self, outlives_env, deeply_normalize_ty))] + #[instrument(level = "debug", skip(self, outlives_env))] pub fn process_registered_region_obligations( &self, outlives_env: &OutlivesEnvironment<'tcx>, - mut deeply_normalize_ty: impl FnMut( - PolyTypeOutlivesPredicate<'tcx>, - SubregionOrigin<'tcx>, - ) - -> Result, NoSolution>, span: Span, - ) -> Result<(), (PolyTypeOutlivesPredicate<'tcx>, SubregionOrigin<'tcx>)> { + ) { assert!(!self.in_snapshot(), "cannot process registered region obligations in a snapshot"); if self.tcx.assumptions_on_binders() { @@ -322,17 +315,10 @@ impl<'tcx> InferCtxt<'tcx> { } for TypeOutlivesConstraint { sup_type, sub_region, origin } in my_region_obligations { - let outlives = ty::Binder::dummy(ty::OutlivesPredicate(sup_type, sub_region)); - let ty::OutlivesPredicate(sup_type, sub_region) = - deeply_normalize_ty(outlives, origin.clone()) - .map_err(|NoSolution| (outlives, origin.clone()))? - .no_bound_vars() - .expect("started with no bound vars, should end with no bound vars"); // `TypeOutlives` is structural, so we should try to opportunistically resolve all // region vids before processing regions, so we have a better chance to match clauses // in our param-env. - let (sup_type, sub_region) = - (sup_type, sub_region).fold_with(&mut OpportunisticRegionResolver::new(self)); + let (sup_type, sub_region) = eager_resolve_vars(self, (sup_type, sub_region)); if self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions && outlives_env @@ -355,8 +341,6 @@ impl<'tcx> InferCtxt<'tcx> { outlives.type_must_outlive(origin, sup_type, sub_region, category); } } - - Ok(()) } } @@ -435,6 +419,8 @@ where category: ConstraintCategory<'tcx>, ) { assert!(!ty.has_escaping_bound_vars()); + debug_assert!(!ty.has_non_region_infer()); + debug_assert!(!self.tcx.next_trait_solver_globally() || !ty.has_non_rigid_aliases()); let mut components = smallvec![]; push_outlives_components(self.tcx, ty, &mut components); diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index 195f08dfafd59..8a869df067301 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -23,7 +23,7 @@ mod trait_goals; use derive_where::derive_where; use rustc_type_ir::inherent::*; pub use rustc_type_ir::solve::*; -use rustc_type_ir::{self as ty, Interner, Region}; +use rustc_type_ir::{self as ty, Interner, Region, TypeVisitableExt}; use tracing::instrument; pub use self::eval_ctxt::{ @@ -90,16 +90,25 @@ where goal: Goal>, ) -> QueryResultOrRerunNonErased { let ty::OutlivesPredicate(ty, lt) = goal.predicate; + let ty = self.normalize(GoalSource::Misc, goal.param_env, ty::Unnormalized::new_wip(ty))?; if self.cx().assumptions_on_binders() { - // FIXME(-Zassumptions-on-binders): we need to normalize `ty` let constraint = self.destructure_type_outlives(ty, lt); self.register_solver_region_constraint(constraint); } else { self.register_ty_outlives(ty, lt); } - self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + // The normalized type can still contain non-rigid higher ranked aliases if their + // normalization ends up with ambiguity. Or we have non-rigid aliases inside rigid ones. + // Infer vars may be resolved to types/consts containing non-rigid aliases later. + // Thus we should stall this goal to avoid registering non-rigid type outlives into + // the outer infcx. + if ty.has_non_region_infer() || ty.has_non_rigid_aliases() { + self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) + } else { + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } } #[instrument(level = "trace", skip(self))] diff --git a/compiler/rustc_trait_selection/src/regions.rs b/compiler/rustc_trait_selection/src/regions.rs index 5ff2c904e94c5..c63b7773739d0 100644 --- a/compiler/rustc_trait_selection/src/regions.rs +++ b/compiler/rustc_trait_selection/src/regions.rs @@ -5,12 +5,9 @@ use rustc_infer::infer::{ InferCtxt, RegionResolutionError, SubregionOrigin, TyCtxtInferExt, TypeOutlivesConstraint, }; use rustc_macros::extension; -use rustc_middle::traits::ObligationCause; -use rustc_middle::traits::query::NoSolution; -use rustc_middle::ty::{self, Ty, TyCtxt, TypingMode, Unnormalized, elaborate}; -use rustc_span::{DUMMY_SP, Span}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, TypingMode, elaborate}; +use rustc_span::DUMMY_SP; -use crate::traits::ScrubbedTraitError; use crate::traits::outlives_bounds::InferCtxtExt; #[extension(pub trait OutlivesEnvironmentBuildExt<'tcx>)] @@ -34,18 +31,8 @@ impl<'tcx> OutlivesEnvironment<'tcx> { let mut bounds = vec![]; for bound in param_env.caller_bounds() { - if let Some(mut type_outlives) = bound.as_type_outlives_clause() { - if infcx.next_trait_solver() { - match crate::solve::deeply_normalize::<_, ScrubbedTraitError<'tcx>>( - infcx.at(&ObligationCause::dummy(), param_env), - Unnormalized::new_wip(type_outlives), - ) { - Ok(new) => type_outlives = new, - Err(_) => { - infcx.dcx().delayed_bug(format!("could not normalize `{bound}`")); - } - } - } + if let Some(type_outlives) = bound.as_type_outlives_clause() { + debug_assert!(!infcx.next_trait_solver() || !type_outlives.has_non_rigid_aliases()); bounds.push(type_outlives); } } @@ -75,14 +62,12 @@ impl<'tcx> OutlivesEnvironment<'tcx> { #[extension(pub trait InferCtxtRegionExt<'tcx>)] impl<'tcx> InferCtxt<'tcx> { - /// Resolve regions, using the deep normalizer to normalize any type-outlives - /// obligations in the process. This is in `rustc_trait_selection` because - /// we need to normalize. - /// - /// Prefer this method over `resolve_regions_with_normalize`, unless you are - /// doing something specific for normalization. + /// Resolve regions lexically. /// /// This function assumes that all infer variables are already constrained. + /// + /// FIXME(#155345): this can probably be moved back to `rustc_infer` now that normalization is + /// no longer required. These two extension traits won't be needed then. fn resolve_regions( &self, body_def_id: LocalDefId, @@ -94,34 +79,6 @@ impl<'tcx> InferCtxt<'tcx> { self.tcx.def_span(body_def_id), ) } - - /// Don't call this directly unless you know what you're doing. - fn resolve_regions_with_outlives_env( - &self, - outlives_env: &OutlivesEnvironment<'tcx>, - span: Span, - ) -> Vec> { - self.resolve_regions_with_normalize( - &outlives_env, - |ty, origin| { - let ty = self.resolve_vars_if_possible(ty); - - if self.next_trait_solver() { - crate::solve::deeply_normalize( - self.at( - &ObligationCause::dummy_with_span(origin.span()), - outlives_env.param_env, - ), - Unnormalized::new_wip(ty), - ) - .map_err(|_: Vec>| NoSolution) - } else { - Ok(ty) - } - }, - span, - ) - } } /// Given a known `param_env` and a set of well formed types, can we prove that diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index 605f4d6758a5a..47a147b595a1d 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -1,7 +1,7 @@ use std::collections::hash_map::Entry; use std::ops::Deref; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::LangItem; use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; use rustc_infer::infer::canonical::query_response::make_query_region_constraints; @@ -16,7 +16,8 @@ use rustc_infer::traits::solve::{ use rustc_middle::traits::query::NoSolution; use rustc_middle::traits::solve::Certainty; use rustc_middle::ty::{ - self, MayBeErased, Ty, TyCtxt, TypeFlags, TypeFoldable, TypeVisitableExt, TypingMode, + self, MayBeErased, Ty, TyCtxt, TypeFlags, TypeFoldable, TypeSuperVisitable, TypeVisitable, + TypeVisitableExt, TypeVisitor, TypingMode, }; use rustc_next_trait_solver::solve::{GoalStalledOn, GoalStalledOnOpaques}; use rustc_span::{DUMMY_SP, Span}; @@ -87,6 +88,33 @@ fn goal_stalled_on_args_or_nonempty_opaques<'tcx>( } } +struct CollectNonRegionInfer<'tcx> { + infers: Vec>, + visited: FxHashSet>, +} + +impl<'tcx> TypeVisitor> for CollectNonRegionInfer<'tcx> { + fn visit_ty(&mut self, ty: Ty<'tcx>) { + if self.visited.contains(&ty) { + return; + } + + match ty.kind() { + ty::Infer(_) => self.infers.push(ty.into()), + _ => ty.super_visit_with(self), + } + + self.visited.insert(ty); + } + + fn visit_const(&mut self, ct: ty::Const<'tcx>) { + match ct.kind() { + ty::ConstKind::Infer(_) => self.infers.push(ct.into()), + _ => ct.super_visit_with(self), + } + } +} + impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate<'tcx> { type Infcx = InferCtxt<'tcx>; type Interner = TyCtxt<'tcx>; @@ -189,6 +217,21 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< return Outcome::NoFastPath; } + let ty = self.resolve_vars_if_possible(outlives.0); + let mut infer_collector = CollectNonRegionInfer { + infers: Default::default(), + visited: Default::default(), + }; + ty.visit_with(&mut infer_collector); + let infers = infer_collector.infers; + if !infers.is_empty() { + return goal_stalled_on_args(infers); + } + + if ty.has_non_rigid_aliases() { + return Outcome::NoFastPath; + } + self.0.register_type_outlives_constraint( outlives.0, outlives.1, diff --git a/compiler/rustc_trait_selection/src/traits/auto_trait.rs b/compiler/rustc_trait_selection/src/traits/auto_trait.rs index 691290f75495b..c959b6856ca70 100644 --- a/compiler/rustc_trait_selection/src/traits/auto_trait.rs +++ b/compiler/rustc_trait_selection/src/traits/auto_trait.rs @@ -173,8 +173,7 @@ impl<'tcx> AutoTraitFinder<'tcx> { } let outlives_env = OutlivesEnvironment::new(&infcx, CRATE_DEF_ID, full_env, []); - let _ = - infcx.process_registered_region_obligations(&outlives_env, |ty, _| Ok(ty), DUMMY_SP); + let _ = infcx.process_registered_region_obligations(&outlives_env, DUMMY_SP); let region_data = infcx.inner.borrow_mut().unwrap_region_constraints().data().clone(); diff --git a/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.rs b/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.rs index 5c13a871a7b8d..70c1d9713d0a9 100644 --- a/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.rs +++ b/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.rs @@ -86,4 +86,5 @@ fn foo() {} fn main() { foo::<&_>(); //~^ ERROR overflow evaluating the requirement `&_: Typed` + //~| ERROR: type annotations needed: cannot satisfy `_: '_` } diff --git a/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.stderr b/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.stderr index d350eb0f7795c..133bfd7140c9c 100644 --- a/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.stderr +++ b/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.stderr @@ -1,3 +1,9 @@ +error[E0284]: type annotations needed: cannot satisfy `_: '_` + --> $DIR/cycle-modulo-ambig-aliases.rs:87:11 + | +LL | foo::<&_>(); + | ^^ cannot satisfy `_: '_` + error[E0275]: overflow evaluating the requirement `&_: Typed` --> $DIR/cycle-modulo-ambig-aliases.rs:87:11 | @@ -10,6 +16,7 @@ note: required by a bound in `foo` LL | fn foo() {} | ^^^^^ required by this bound in `foo` -error: aborting due to 1 previous error +error: aborting due to 2 previous errors -For more information about this error, try `rustc --explain E0275`. +Some errors have detailed explanations: E0275, E0284. +For more information about an error, try `rustc --explain E0275`. From 7c1c57179de7152fddf0b61a37a94c7659b76815 Mon Sep 17 00:00:00 2001 From: Adwin White Date: Thu, 23 Jul 2026 13:51:02 +0800 Subject: [PATCH 08/28] fix building std with next solver --- .../src/check/always_applicable.rs | 2 +- .../rustc_hir_analysis/src/outlives/utils.rs | 4 +-- .../src/infer/outlives/for_liveness.rs | 8 ++---- .../src/infer/outlives/obligations.rs | 10 ++++++-- .../src/infer/outlives/test_type_match.rs | 17 +++---------- .../rustc_infer/src/infer/outlives/verify.rs | 25 ++++++------------- .../src/infer/region_constraints/mod.rs | 8 +++--- .../eval_ctxt/solver_region_constraints.rs | 3 ++- .../query/type_op/implied_outlives_bounds.rs | 17 +++++++++---- compiler/rustc_type_ir/src/binder.rs | 8 ++++++ compiler/rustc_type_ir/src/elaborate.rs | 8 +++--- compiler/rustc_type_ir/src/outlives.rs | 9 ++++--- 12 files changed, 59 insertions(+), 60 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/always_applicable.rs b/compiler/rustc_hir_analysis/src/check/always_applicable.rs index b94a8cd5e6d63..9a8008214ed04 100644 --- a/compiler/rustc_hir_analysis/src/check/always_applicable.rs +++ b/compiler/rustc_hir_analysis/src/check/always_applicable.rs @@ -282,7 +282,7 @@ fn ensure_impl_predicates_are_implied_by_item_defn<'tcx>( // reference the params from the ADT instead of from the impl which is bad UX. To resolve // this we "rename" the ADT's params to be the impl's params which should not affect behaviour. let impl_adt_ty = Ty::new_adt(tcx, tcx.adt_def(adt_def_id), adt_to_impl_args); - let adt_env = ty::EarlyBinder::bind(tcx, tcx.param_env(adt_def_id)) + let adt_env = ty::EarlyBinder::bind_unchecked(tcx.param_env(adt_def_id)) .instantiate(tcx, adt_to_impl_args) .skip_norm_wip(); diff --git a/compiler/rustc_hir_analysis/src/outlives/utils.rs b/compiler/rustc_hir_analysis/src/outlives/utils.rs index ac58a37cc8e81..0157703532268 100644 --- a/compiler/rustc_hir_analysis/src/outlives/utils.rs +++ b/compiler/rustc_hir_analysis/src/outlives/utils.rs @@ -83,7 +83,7 @@ pub(crate) fn insert_outlives_predicate<'tcx>( span_bug!(span, "Should not deduce placeholder outlives component"); } - Component::Alias(alias_ty) => { + Component::Alias(is_rigid, alias_ty) => { // This would either arise from something like: // // ``` @@ -102,7 +102,7 @@ pub(crate) fn insert_outlives_predicate<'tcx>( // // Here we want to add an explicit `where ::Item: 'a` // or `Opaque: 'a` depending on the alias kind. - let ty = alias_ty.to_ty(tcx, ty::IsRigid::No); + let ty = alias_ty.to_ty(tcx, is_rigid); required_predicates .entry(ty::OutlivesPredicate(ty.into(), outlived_region)) .or_insert(span); diff --git a/compiler/rustc_infer/src/infer/outlives/for_liveness.rs b/compiler/rustc_infer/src/infer/outlives/for_liveness.rs index 15756f03f25f9..1ad0c5d192cf1 100644 --- a/compiler/rustc_infer/src/infer/outlives/for_liveness.rs +++ b/compiler/rustc_infer/src/infer/outlives/for_liveness.rs @@ -56,7 +56,7 @@ where // either `'static` or a unique outlives region, and if one is // found, we just need to prove that that region is still live. // If one is not found, then we continue to walk through the alias. - ty::Alias(_, alias_ty @ ty::AliasTy { kind, args, .. }) => { + ty::Alias(is_rigid, alias_ty @ ty::AliasTy { kind, args, .. }) => { let tcx = self.tcx; let param_env = self.param_env; let def_id = match kind { @@ -82,11 +82,7 @@ where &outlives.map_bound(|ty::OutlivesPredicate(ty, bound)| { VerifyIfEq { ty, bound } }), - // FIXME(#155345): Region handling should generally only - // deal with rigid aliases, making sure we do so correctly - // everywhere is effort, so we're just using `No` everywhere - // for now. This should change soon. - alias_ty.to_ty(tcx, ty::IsRigid::No), + alias_ty.to_ty(tcx, is_rigid), ) } }) diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index c2753d7499556..058aaa017cad4 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -420,7 +420,10 @@ where ) { assert!(!ty.has_escaping_bound_vars()); debug_assert!(!ty.has_non_region_infer()); - debug_assert!(!self.tcx.next_trait_solver_globally() || !ty.has_non_rigid_aliases()); + debug_assert!( + !self.tcx.next_trait_solver_globally() || !ty.has_non_rigid_aliases(), + "{ty:?} has non-rigid aliases" + ); let mut components = smallvec![]; push_outlives_components(self.tcx, ty, &mut components); @@ -446,7 +449,10 @@ where Component::Placeholder(placeholder_ty) => { self.placeholder_ty_must_outlive(origin, region, *placeholder_ty); } - Component::Alias(alias_ty) => self.alias_ty_must_outlive(origin, region, *alias_ty), + Component::Alias(is_rigid, alias_ty) => { + debug_assert_eq!(*is_rigid, ty::IsRigid::yes_if_next_solver(self.tcx)); + self.alias_ty_must_outlive(origin, region, *alias_ty); + } Component::EscapingAlias(subcomponents) => { self.components_must_outlive(origin, subcomponents, region, category); } diff --git a/compiler/rustc_infer/src/infer/outlives/test_type_match.rs b/compiler/rustc_infer/src/infer/outlives/test_type_match.rs index b84c6d0aed42d..a90f7d58847e3 100644 --- a/compiler/rustc_infer/src/infer/outlives/test_type_match.rs +++ b/compiler/rustc_infer/src/infer/outlives/test_type_match.rs @@ -44,13 +44,10 @@ pub fn extract_verify_if_eq<'tcx>( assert!(!verify_if_eq_b.has_escaping_bound_vars()); let mut m = MatchAgainstHigherRankedOutlives::new(tcx); let verify_if_eq = verify_if_eq_b.skip_binder(); - // FIXME(#155345): Region handling should generally only - // deal with rigid aliases, making sure we do so correctly - // everywhere is effort, so we're just using `No` everywhere - // for now. This should change soon. - let (verify_ty, test_ty) = - ty::set_aliases_to_non_rigid(tcx, (verify_if_eq.ty, test_ty)).skip_norm_wip(); - m.relate(verify_ty, test_ty).ok()?; + debug_assert!( + !tcx.next_trait_solver_globally() || !(verify_if_eq.ty, test_ty).has_non_rigid_aliases() + ); + m.relate(verify_if_eq.ty, test_ty).ok()?; if let ty::RegionKind::ReBound(index_kind, br) = verify_if_eq.bound.kind() { assert!(matches!(index_kind, ty::BoundVarIndexKind::Bound(ty::INNERMOST))); @@ -86,12 +83,6 @@ pub(super) fn can_match_erased_ty<'tcx>( assert!(!outlives_predicate.has_escaping_bound_vars()); let erased_outlives_predicate = tcx.erase_and_anonymize_regions(outlives_predicate); let outlives_ty = erased_outlives_predicate.skip_binder().0; - // FIXME(#155345): Region handling should generally only - // deal with rigid aliases, making sure we do so correctly - // everywhere is effort, so we're just using `No` everywhere - // for now. This should change soon. - let (outlives_ty, erased_ty) = - ty::set_aliases_to_non_rigid(tcx, (outlives_ty, erased_ty)).skip_normalization(); if outlives_ty == erased_ty { // pointless micro-optimization true diff --git a/compiler/rustc_infer/src/infer/outlives/verify.rs b/compiler/rustc_infer/src/infer/outlives/verify.rs index 3c07a32471bdb..f1e927912ef51 100644 --- a/compiler/rustc_infer/src/infer/outlives/verify.rs +++ b/compiler/rustc_infer/src/infer/outlives/verify.rs @@ -96,14 +96,8 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> { &self, alias_ty: ty::AliasTy<'tcx>, ) -> Vec> { - // FIXME(#155345): Region handling should generally only - // deal with rigid aliases, making sure we do so correctly - // everywhere is effort, so we're just using `No` everywhere - // for now. This should change soon. let erased_alias_ty = self.tcx.erase_and_anonymize_regions( - ty::set_aliases_to_non_rigid(self.tcx, alias_ty) - .skip_norm_wip() - .to_ty(self.tcx, ty::IsRigid::No), + alias_ty.to_ty(self.tcx, ty::IsRigid::yes_if_next_solver(self.tcx)), ); self.declared_generic_bounds_from_env_for_erased_ty(erased_alias_ty) } @@ -168,7 +162,8 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> { Component::Placeholder(placeholder_ty) => { self.param_or_placeholder_bound(Ty::new_placeholder(self.tcx, placeholder_ty)) } - Component::Alias(alias_ty) => self.alias_bound(alias_ty), + // `type_must_outlive` already asserted that it's rigid in the next solver. + Component::Alias(_, alias_ty) => self.alias_bound(alias_ty), Component::EscapingAlias(ref components) => self.bound_from_components(components), Component::UnresolvedInferenceVariable(v) => { // Ignore this, we presume it will yield an error later, since @@ -245,20 +240,14 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> { // And therefore we can safely use structural equality for alias types. (GenericKind::Param(p1), ty::Param(p2)) if p1 == p2 => {} (GenericKind::Placeholder(p1), ty::Placeholder(p2)) if p1 == p2 => {} - // FIXME(#155345): We probably want to assert that the rhs is rigid. - (GenericKind::Alias(a1), ty::Alias(_, a2)) if a1.kind == a2.kind => {} + (GenericKind::Alias(a1), ty::Alias(is_rigid, a2)) if a1.kind == a2.kind => { + debug_assert_eq!(*is_rigid, ty::IsRigid::yes_if_next_solver(self.tcx)); + } _ => return None, } let p_ty = p.to_ty(tcx); - // FIXME(#155345): Region handling should generally only - // deal with rigid aliases, making sure we do so correctly - // everywhere is effort, so we're just using `No` everywhere - // for now. This should change soon. - let erased_p_ty = self.tcx.erase_and_anonymize_regions( - ty::set_aliases_to_non_rigid(self.tcx, p_ty).skip_norm_wip(), - ); - let erased_ty = ty::set_aliases_to_non_rigid(self.tcx, erased_ty).skip_norm_wip(); + let erased_p_ty = self.tcx.erase_and_anonymize_regions(p_ty); (erased_p_ty == erased_ty).then_some(ty::Binder::dummy(ty::OutlivesPredicate(p_ty, r))) })); diff --git a/compiler/rustc_infer/src/infer/region_constraints/mod.rs b/compiler/rustc_infer/src/infer/region_constraints/mod.rs index 32ed93564bd37..ef77c9b17bba8 100644 --- a/compiler/rustc_infer/src/infer/region_constraints/mod.rs +++ b/compiler/rustc_infer/src/infer/region_constraints/mod.rs @@ -190,6 +190,8 @@ pub struct Verify<'tcx> { pub enum GenericKind<'tcx> { Param(ty::ParamTy), Placeholder(ty::PlaceholderType<'tcx>), + // FIXME: we expect this alias to be rigid in the next solver. + // But we can't assert that in construction since this enum is public. Alias(ty::AliasTy<'tcx>), } @@ -807,11 +809,7 @@ impl<'tcx> GenericKind<'tcx> { match *self { GenericKind::Param(ref p) => p.to_ty(tcx), GenericKind::Placeholder(ref p) => Ty::new_placeholder(tcx, *p), - // FIXME(#155345): Region handling should generally only - // deal with rigid aliases, making sure we do so correctly - // everywhere is effort, so we're just using `No` everywhere - // for now. This should change soon. - GenericKind::Alias(ref p) => p.to_ty(tcx, ty::IsRigid::No), + GenericKind::Alias(ref p) => p.to_ty(tcx, ty::IsRigid::yes_if_next_solver(tcx)), } } } diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs index 129a7b0f0de78..f31f292240a56 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs @@ -179,7 +179,8 @@ where Placeholder(p) => { RegionConstraint::PlaceholderTyOutlives(Ty::new_placeholder(self.cx(), *p), r) } - Alias(alias) => self.destructure_alias_outlives(*alias, r), + // The alias is either rigid or ambiguous in which case we'll return with ambiguity. + Alias(_, alias) => self.destructure_alias_outlives(*alias, r), UnresolvedInferenceVariable(_) => RegionConstraint::Ambiguity, Param(_) => panic!("Params should have been canonicalized to placeholders"), EscapingAlias(components) => self.destructure_components(components, r), diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs index a98f8b9a9af88..c9de97ea82c11 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs @@ -61,6 +61,8 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>( "compute_implied_outlives_bounds assumes region obligations are empty before starting" ); + let tcx = ocx.infcx.tcx; + // FIXME: This doesn't seem right. All call sites already normalize `ty`: // - `Ty`s from the `DefiningTy` in Borrowck: we have to normalize in the caller // in order to get implied bounds involving any unconstrained region vars @@ -141,8 +143,8 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>( r_b, ))) => { let mut components = smallvec![]; - push_outlives_components(ocx.infcx.tcx, ty_a, &mut components); - outlives_bounds.extend(implied_bounds_from_components(r_b, components)) + push_outlives_components(tcx, ty_a, &mut components); + outlives_bounds.extend(implied_bounds_from_components(tcx, r_b, components)) } } } @@ -159,8 +161,8 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>( ocx.infcx.clone_registered_region_obligations() { let mut components = smallvec![]; - push_outlives_components(ocx.infcx.tcx, sup_type, &mut components); - outlives_bounds.extend(implied_bounds_from_components(sub_region, components)); + push_outlives_components(tcx, sup_type, &mut components); + outlives_bounds.extend(implied_bounds_from_components(tcx, sub_region, components)); } } @@ -197,6 +199,7 @@ impl<'tcx> TypeVisitor> for ContainsBevyParamSet<'tcx> { /// `T: 'a` to hold. We get to assume that the caller has validated /// those relationships. fn implied_bounds_from_components<'tcx>( + tcx: TyCtxt<'tcx>, sub_region: ty::Region<'tcx>, sup_components: SmallVec<[Component>; 4]>, ) -> Vec> { @@ -206,7 +209,11 @@ fn implied_bounds_from_components<'tcx>( match component { Component::Region(r) => Some(OutlivesBound::RegionSubRegion(sub_region, r)), Component::Param(p) => Some(OutlivesBound::RegionSubParam(sub_region, p)), - Component::Alias(p) => Some(OutlivesBound::RegionSubAlias(sub_region, p)), + Component::Alias(is_rigid, p) => { + // We expect them to be already deeply normalized. + debug_assert_eq!(is_rigid, ty::IsRigid::yes_if_next_solver(tcx)); + Some(OutlivesBound::RegionSubAlias(sub_region, p)) + } Component::Placeholder(_p) => { // FIXME(non_lifetime_binders): Placeholders don't currently // imply anything for outlives, though they could easily. diff --git a/compiler/rustc_type_ir/src/binder.rs b/compiler/rustc_type_ir/src/binder.rs index a3ba407b4d7c7..c11532c798a42 100644 --- a/compiler/rustc_type_ir/src/binder.rs +++ b/compiler/rustc_type_ir/src/binder.rs @@ -354,6 +354,14 @@ impl> EarlyBinder { } } +impl EarlyBinder { + /// Use `bind/bind_iter/bind_no_rigid_aliases` instead. + /// Don't use this unless you know what you're doing. + pub fn bind_unchecked(value: T) -> EarlyBinder { + EarlyBinder { value, _tcx: PhantomData } + } +} + impl EarlyBinder { pub fn as_ref(&self) -> EarlyBinder { EarlyBinder { value: &self.value, _tcx: PhantomData } diff --git a/compiler/rustc_type_ir/src/elaborate.rs b/compiler/rustc_type_ir/src/elaborate.rs index a22f558fe5b54..03d5fc1ff8330 100644 --- a/compiler/rustc_type_ir/src/elaborate.rs +++ b/compiler/rustc_type_ir/src/elaborate.rs @@ -274,11 +274,11 @@ fn elaborate_component_to_clause( Component::UnresolvedInferenceVariable(_) => None, - Component::Alias(alias_ty) => { + Component::Alias(is_rigid, alias_ty) => { // We might end up here if we have `Foo<::Assoc>: 'a`. // With this, we can deduce that `::Assoc: 'a`. Some(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate( - alias_ty.to_ty(cx, ty::IsRigid::No), + alias_ty.to_ty(cx, is_rigid), outlives_region, ))) } @@ -415,9 +415,9 @@ pub fn elaborate_outlives_assumptions( collected.insert(ty::OutlivesPredicate(ty.into(), r2)); } - Component::Alias(alias_ty) => { + Component::Alias(is_rigid, alias_ty) => { collected.insert(ty::OutlivesPredicate( - alias_ty.to_ty(cx, ty::IsRigid::No).into(), + alias_ty.to_ty(cx, is_rigid).into(), r2, )); } diff --git a/compiler/rustc_type_ir/src/outlives.rs b/compiler/rustc_type_ir/src/outlives.rs index 1474e65a9ccbf..1494da29527c4 100644 --- a/compiler/rustc_type_ir/src/outlives.rs +++ b/compiler/rustc_type_ir/src/outlives.rs @@ -26,7 +26,10 @@ pub enum Component { // is not in a position to judge which is the best technique, so // we just product the projection as a component and leave it to // the consumer to decide (but see `EscapingProjection` below). - Alias(ty::AliasTy), + // + // We have to track rigidness because it's also used in param env + // elaboration where things are not normalized yet. + Alias(ty::IsRigid, ty::AliasTy), // In the case where a projection has escaping regions -- meaning // regions bound within the type itself -- we always use @@ -149,7 +152,7 @@ impl TypeVisitor for OutlivesCollector<'_, I> { // trait-ref. Therefore, if we see any higher-ranked regions, // we simply fallback to the most restrictive rule, which // requires that `Pi: 'a` for all `i`. - ty::Alias(_, alias_ty) => { + ty::Alias(is_rigid, alias_ty) => { if !alias_ty.has_escaping_bound_vars() { // best case: no escaping regions, so push the // projection and skip the subtree (thus generating no @@ -157,7 +160,7 @@ impl TypeVisitor for OutlivesCollector<'_, I> { // the rules OutlivesProjectionEnv, // OutlivesProjectionTraitDef, and // OutlivesProjectionComponents to regionck. - self.out.push(Component::Alias(alias_ty)); + self.out.push(Component::Alias(is_rigid, alias_ty)); } else { // fallback case: hard code // OutlivesProjectionComponents. Continue walking From bb3fba0abf0d7681a46ce67faa01f290ea677b9f Mon Sep 17 00:00:00 2001 From: Schneems Date: Thu, 23 Jul 2026 18:20:50 -0500 Subject: [PATCH 09/28] Expand fs::metadata/fs::symlink_metadata behavior Several changes: ## Link `symlink_metadata` from `metadata` Someone reading `metadata` likely wants easy/quick access to `symlink_metadata` as well. Since this explicitly documents that it does follow symlinks, it made sense to leave a note stating how to perform the same action without that behavior. ## Add symbolic link note to errors It's intuitive that you cannot return the `Metadata` of a resolved file if the file could not be resolved. But it's not always obvious. Since we're stating "the path does not exist" (also an obvious case). It will help to have this gentle reminder. ## Explicit behavior of directories and files in `symlink_metadata` Reading the documentation, it was unclear whether `symlink_metadata` would also work on files and directories. I guessed it would, but it's nice to call it out. The wording also gave me an opportunity to cross-link to `metadata` so someone can easily jump back and forth between them. --- library/std/src/fs.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 0622637e72738..1f16265094518 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -2769,7 +2769,8 @@ pub fn remove_file>(path: P) -> io::Result<()> { /// directory, etc. /// /// This function will traverse symbolic links to query information about the -/// destination file. +/// destination file. To query metadata about the path itself without following +/// symbolic links, use [`symlink_metadata`]. /// /// # Platform-specific behavior /// @@ -2786,6 +2787,7 @@ pub fn remove_file>(path: P) -> io::Result<()> { /// /// * The user lacks permissions to perform `metadata` call on `path`. /// * `path` does not exist. +/// * `path` is a symbolic link, but the destination file cannot be resolved. /// /// # Examples /// @@ -2806,6 +2808,11 @@ pub fn metadata>(path: P) -> io::Result { /// Queries the metadata about a file without following symlinks. /// +/// This function will return the [`Metadata`] of the exact path without +/// traversing symbolic links to a resolved destination file. Using this function +/// on a path that is a file or directory (not a symbolic link) will behave the +/// same as [`metadata`]. +/// /// # Platform-specific behavior /// /// This function currently corresponds to the `lstat` function on Unix From b2c8b50b1ddaefc5d5403a664d5c0abcba1eee46 Mon Sep 17 00:00:00 2001 From: Gri-ffin Date: Fri, 24 Jul 2026 14:17:08 +0100 Subject: [PATCH 10/28] use PostBorrowck typing mode in LateContext --- compiler/rustc_lint/src/context.rs | 2 +- compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index e992f1ed385e7..62db3208d521b 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -645,7 +645,7 @@ impl<'tcx> LateContext<'tcx> { && self.tcx.use_typing_mode_post_typeck_until_borrowck() { let def_id = self.tcx.hir_enclosing_body_owner(body_id.hir_id); - TypingMode::borrowck(self.tcx, def_id) + TypingMode::post_borrowck_analysis(self.tcx, def_id) } else { TypingMode::non_body_analysis() } diff --git a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs index ed71ff6a463db..61578ee2892d5 100644 --- a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs +++ b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs @@ -84,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { } let def_id = opaque.def_id.to_def_id(); - let infcx = &cx.tcx.infer_ctxt().ignoring_regions().build(cx.typing_mode()); + let infcx = &cx.tcx.infer_ctxt().build(cx.typing_mode()); // For every projection predicate in the opaque type's explicit bounds, // check that the type that we're assigning actually satisfies the bounds // of the associated type. From 176ea43bce4c11aa3e3d507f4f808fdc5714437e Mon Sep 17 00:00:00 2001 From: Gri-ffin Date: Sat, 25 Jul 2026 11:14:20 +0100 Subject: [PATCH 11/28] add next solver revision test for future_not_send --- ....stderr => future_not_send.current.stderr} | 36 ++++----- .../tests/ui/future_not_send.next.stderr | 75 +++++++++++++++++++ src/tools/clippy/tests/ui/future_not_send.rs | 3 + 3 files changed, 96 insertions(+), 18 deletions(-) rename src/tools/clippy/tests/ui/{future_not_send.stderr => future_not_send.current.stderr} (88%) create mode 100644 src/tools/clippy/tests/ui/future_not_send.next.stderr diff --git a/src/tools/clippy/tests/ui/future_not_send.stderr b/src/tools/clippy/tests/ui/future_not_send.current.stderr similarity index 88% rename from src/tools/clippy/tests/ui/future_not_send.stderr rename to src/tools/clippy/tests/ui/future_not_send.current.stderr index 8b8af1ebaed39..471452d9a416c 100644 --- a/src/tools/clippy/tests/ui/future_not_send.stderr +++ b/src/tools/clippy/tests/ui/future_not_send.current.stderr @@ -1,11 +1,11 @@ error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:8:62 + --> tests/ui/future_not_send.rs:11:62 | LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { | ^^^^ future returned by `private_future` is not `Send` | note: future is not `Send` as this value is used across an await - --> tests/ui/future_not_send.rs:11:20 + --> tests/ui/future_not_send.rs:14:20 | LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { | -- has type `std::rc::Rc<[u8]>` which is not `Send` @@ -14,7 +14,7 @@ LL | async { true }.await | ^^^^^ await occurs here, with `rc` maybe used later = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` note: captured value is not `Send` because `&` references cannot be sent unless their referent is `Sync` - --> tests/ui/future_not_send.rs:8:39 + --> tests/ui/future_not_send.rs:11:39 | LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { | ^^^^ has type `&std::cell::Cell` which is not `Send`, because `std::cell::Cell` is not `Sync` @@ -23,13 +23,13 @@ LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { = help: to override `-D warnings` add `#[allow(clippy::future_not_send)]` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:14:41 + --> tests/ui/future_not_send.rs:17:41 | LL | pub async fn public_future(rc: Rc<[u8]>) { | ^ future returned by `public_future` is not `Send` | note: future is not `Send` as this value is used across an await - --> tests/ui/future_not_send.rs:17:20 + --> tests/ui/future_not_send.rs:20:20 | LL | pub async fn public_future(rc: Rc<[u8]>) { | -- has type `std::rc::Rc<[u8]>` which is not `Send` @@ -39,45 +39,45 @@ LL | async { true }.await; = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:24:63 + --> tests/ui/future_not_send.rs:27:63 | LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { | ^^^^ future returned by `private_future2` is not `Send` | note: captured value is not `Send` - --> tests/ui/future_not_send.rs:24:26 + --> tests/ui/future_not_send.rs:27:26 | LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { | ^^ has type `std::rc::Rc<[u8]>` which is not `Send` = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` note: captured value is not `Send` because `&` references cannot be sent unless their referent is `Sync` - --> tests/ui/future_not_send.rs:24:40 + --> tests/ui/future_not_send.rs:27:40 | LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { | ^^^^ has type `&std::cell::Cell` which is not `Send`, because `std::cell::Cell` is not `Sync` = note: `std::cell::Cell` doesn't implement `std::marker::Sync` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:30:42 + --> tests/ui/future_not_send.rs:33:42 | LL | pub async fn public_future2(rc: Rc<[u8]>) {} | ^ future returned by `public_future2` is not `Send` | note: captured value is not `Send` - --> tests/ui/future_not_send.rs:30:29 + --> tests/ui/future_not_send.rs:33:29 | LL | pub async fn public_future2(rc: Rc<[u8]>) {} | ^^ has type `std::rc::Rc<[u8]>` which is not `Send` = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:42:39 + --> tests/ui/future_not_send.rs:45:39 | LL | async fn private_future(&self) -> usize { | ^^^^^ future returned by `private_future` is not `Send` | note: future is not `Send` as this value is used across an await - --> tests/ui/future_not_send.rs:45:24 + --> tests/ui/future_not_send.rs:48:24 | LL | async fn private_future(&self) -> usize { | ----- has type `&Dummy` which is not `Send` @@ -87,26 +87,26 @@ LL | async { true }.await; = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:49:38 + --> tests/ui/future_not_send.rs:52:38 | LL | pub async fn public_future(&self) { | ^ future returned by `public_future` is not `Send` | note: captured value is not `Send` because `&` references cannot be sent unless their referent is `Sync` - --> tests/ui/future_not_send.rs:49:32 + --> tests/ui/future_not_send.rs:52:32 | LL | pub async fn public_future(&self) { | ^^^^^ has type `&Dummy` which is not `Send`, because `Dummy` is not `Sync` = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:61:37 + --> tests/ui/future_not_send.rs:64:37 | LL | async fn generic_future(t: T) -> T | ^ future returned by `generic_future` is not `Send` | note: future is not `Send` as this value is used across an await - --> tests/ui/future_not_send.rs:67:20 + --> tests/ui/future_not_send.rs:70:20 | LL | let rt = &t; | -- has type `&T` which is not `Send` @@ -115,13 +115,13 @@ LL | async { true }.await; = note: `T` doesn't implement `std::marker::Sync` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:83:51 + --> tests/ui/future_not_send.rs:86:51 | LL | async fn generic_future_always_unsend(_: Rc) { | ^ future returned by `generic_future_always_unsend` is not `Send` | note: future is not `Send` as this value is used across an await - --> tests/ui/future_not_send.rs:86:20 + --> tests/ui/future_not_send.rs:89:20 | LL | async fn generic_future_always_unsend(_: Rc) { | - has type `std::rc::Rc` which is not `Send` diff --git a/src/tools/clippy/tests/ui/future_not_send.next.stderr b/src/tools/clippy/tests/ui/future_not_send.next.stderr new file mode 100644 index 0000000000000..e5cacd6c8b3bf --- /dev/null +++ b/src/tools/clippy/tests/ui/future_not_send.next.stderr @@ -0,0 +1,75 @@ +error: future cannot be sent between threads safely + --> tests/ui/future_not_send.rs:11:62 + | +LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { + | ^^^^ + | + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` + = note: `-D clippy::future-not-send` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::future_not_send)]` + +error: future cannot be sent between threads safely + --> tests/ui/future_not_send.rs:17:41 + | +LL | pub async fn public_future(rc: Rc<[u8]>) { + | ^ + | + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` + +error: future cannot be sent between threads safely + --> tests/ui/future_not_send.rs:27:63 + | +LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { + | ^^^^ + | + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` + +error: future cannot be sent between threads safely + --> tests/ui/future_not_send.rs:33:42 + | +LL | pub async fn public_future2(rc: Rc<[u8]>) {} + | ^ + | + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` + +error: future cannot be sent between threads safely + --> tests/ui/future_not_send.rs:45:39 + | +LL | async fn private_future(&self) -> usize { + | ^^^^^ + | + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync` + +error: future cannot be sent between threads safely + --> tests/ui/future_not_send.rs:52:38 + | +LL | pub async fn public_future(&self) { + | ^ + | + = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync` + +error: future cannot be sent between threads safely + --> tests/ui/future_not_send.rs:64:37 + | +LL | async fn generic_future(t: T) -> T + | ^ future returned by `generic_future` is not `Send` + | +note: future is not `Send` as this value is used across an await + --> tests/ui/future_not_send.rs:70:20 + | +LL | let rt = &t; + | -- has type `&T` which is not `Send` +LL | async { true }.await; + | ^^^^^ await occurs here, with `rt` maybe used later + = note: `T` doesn't implement `std::marker::Sync` + +error: future cannot be sent between threads safely + --> tests/ui/future_not_send.rs:86:51 + | +LL | async fn generic_future_always_unsend(_: Rc) { + | ^ + | + = note: `std::rc::Rc` doesn't implement `std::marker::Send` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/future_not_send.rs b/src/tools/clippy/tests/ui/future_not_send.rs index 662ecb9c955de..41d6fc448ffce 100644 --- a/src/tools/clippy/tests/ui/future_not_send.rs +++ b/src/tools/clippy/tests/ui/future_not_send.rs @@ -1,3 +1,6 @@ +//@revisions: current next +//@[next] compile-flags: -Znext-solver=globally + #![warn(clippy::future_not_send)] use std::cell::Cell; From 964e8ee6b7554ee9bb54e43f11833e84365e1590 Mon Sep 17 00:00:00 2001 From: Gri-ffin Date: Sat, 25 Jul 2026 11:16:50 +0100 Subject: [PATCH 12/28] remove lints mention from InferCtxt Drop comment --- compiler/rustc_infer/src/infer/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 72820a31a95b9..eab2a51b24012 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -348,7 +348,7 @@ impl<'tcx> Drop for InferCtxt<'tcx> { // No need for the drop bomb when we're in `TypingMode::PostTypeckUntilBorrowck`, and the `InferCtxt` // doesn't consider regions. This is okay since after typeck, the only reason we care about opaques is - // in relation to regions. In some places *after* typeck that aren't borrowck, like in lints we use + // in relation to regions. In some places *after* typeck that aren't borrowck, we use // `TypingMode::PostTypeckUntilBorrowck` to prevent defining opaque types and we simply don't care about regions. match self.typing_mode_raw() { TypingMode::Coherence From ce472c9f9fccf05cba4529a913532af9413d5a1d Mon Sep 17 00:00:00 2001 From: Max Dexheimer Date: Sun, 26 Jul 2026 11:41:51 +0200 Subject: [PATCH 13/28] Ensure noone can observe the thread spawn hooks being taken --- library/std/src/thread/spawnhook.rs | 14 +++-- .../std/add-spawn-hook-reentrancy-159923.rs | 61 +++++++++++++++++++ 2 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 tests/ui/std/add-spawn-hook-reentrancy-159923.rs diff --git a/library/std/src/thread/spawnhook.rs b/library/std/src/thread/spawnhook.rs index bb36fb687b4af..ec771ef48dede 100644 --- a/library/std/src/thread/spawnhook.rs +++ b/library/std/src/thread/spawnhook.rs @@ -1,7 +1,7 @@ use super::thread::Thread; use crate::cell::Cell; use crate::iter; -use crate::sync::Arc; +use crate::sync::{Arc, UniqueArc}; crate::thread_local! { /// A thread local linked list of spawn hooks. @@ -95,12 +95,14 @@ where G: 'static + Send + FnOnce(), { SPAWN_HOOKS.with(|h| { - let mut hooks = h.take(); - let next = hooks.first.take(); - hooks.first = Some(Arc::new(SpawnHook { + // Perform all fallible operations before taking the current hooks (see #159923) + let mut new_first = UniqueArc::new(SpawnHook { hook: Box::new(move |thread| Box::new(hook(thread))), - next, - })); + next: None, + }); + let mut hooks = h.take(); + new_first.next = hooks.first.take(); + hooks.first = Some(UniqueArc::into_arc(new_first)); h.set(hooks); }); } diff --git a/tests/ui/std/add-spawn-hook-reentrancy-159923.rs b/tests/ui/std/add-spawn-hook-reentrancy-159923.rs new file mode 100644 index 0000000000000..a3714bfda9501 --- /dev/null +++ b/tests/ui/std/add-spawn-hook-reentrancy-159923.rs @@ -0,0 +1,61 @@ +// https://github.com/rust-lang/rust/issues/159923 +//@ edition:2024 +//@ run-pass +//@ needs-threads + +#![feature(alloc_error_hook, thread_spawn_hook)] + +use std::{ + alloc::{GlobalAlloc, Layout, System, set_alloc_error_hook}, + sync::atomic::{AtomicBool, AtomicU32, Ordering}, + thread, +}; + +struct FailingGlobalAlloc; +unsafe impl GlobalAlloc for FailingGlobalAlloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + if FAIL_NEXT_ALLOC.swap(false, Ordering::Relaxed) { + return std::ptr::null_mut(); + } + unsafe { System.alloc(layout) } + } + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + unsafe { System.dealloc(ptr, layout) } + } +} +#[global_allocator] +static ALLOC: FailingGlobalAlloc = FailingGlobalAlloc; + +static FAIL_NEXT_ALLOC: AtomicBool = AtomicBool::new(false); + +fn spawn_and_join() { + thread::scope(|scope| { + scope.spawn(|| {}); + }); +} + +fn main() { + static COUNT: AtomicU32 = AtomicU32::new(0); + + thread::add_spawn_hook(|_| || _ = COUNT.fetch_add(1, Ordering::Relaxed)); + thread::add_spawn_hook(|_| || _ = COUNT.fetch_add(1, Ordering::Relaxed)); + + spawn_and_join(); + spawn_and_join(); + assert_eq!(COUNT.swap(0, Ordering::Relaxed), 4); + + set_alloc_error_hook(|_| { + spawn_and_join(); + assert_eq!(COUNT.swap(0, Ordering::Relaxed), 2); + panic!() + }); + + FAIL_NEXT_ALLOC.store(true, Ordering::Relaxed); + std::panic::catch_unwind(|| { + std::thread::add_spawn_hook(|_| || {}); + }) + .unwrap_err(); + + spawn_and_join(); + assert_eq!(COUNT.swap(0, Ordering::Relaxed), 2); +} From f80e5c1e9b38c1db7b98f1ee2963a684f71e6966 Mon Sep 17 00:00:00 2001 From: Max Dexheimer Date: Sun, 26 Jul 2026 11:45:36 +0200 Subject: [PATCH 14/28] Explicitly document that spawn hooks are not guaranteed to run --- library/std/src/thread/spawnhook.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/std/src/thread/spawnhook.rs b/library/std/src/thread/spawnhook.rs index ec771ef48dede..92fb586d39dcf 100644 --- a/library/std/src/thread/spawnhook.rs +++ b/library/std/src/thread/spawnhook.rs @@ -44,10 +44,12 @@ struct SpawnHook { /// In other words, adding a hook has no effect on already running threads (other than the current /// thread) and the threads they might spawn in the future. /// -/// Hooks can only be added, not removed. -/// /// The hooks will run in reverse order, starting with the most recently added. /// +/// Hooks can only be added, not removed. +/// Note that this does *not* mean that they are guaranteed to run. See [`Builder::no_hooks`]. +/// You cannot rely on a hook running for soundness. +/// /// # Usage /// /// ``` @@ -88,6 +90,8 @@ struct SpawnHook { /// assert_eq!(X.get(), 123); /// }).join().unwrap(); /// ``` +/// +/// [`Builder::no_hooks`]: crate::thread::Builder::no_hooks #[unstable(feature = "thread_spawn_hook", issue = "132951")] pub fn add_spawn_hook(hook: F) where From 216f9d246e6991ad5a4394fd893bb752e5313d28 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 26 Jul 2026 12:50:25 +0200 Subject: [PATCH 15/28] sanitize_standard_fds: Miri supports poll now --- library/std/src/sys/pal/unix/mod.rs | 1 - src/tools/miri/src/shims/files.rs | 161 ++++++++++++++---- .../pass-dep/libc/libc-poll-std-handles.rs | 21 +++ 3 files changed, 145 insertions(+), 38 deletions(-) create mode 100644 src/tools/miri/tests/pass-dep/libc/libc-poll-std-handles.rs diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 75a75ab6bc5fd..1f9b86e1eb75e 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -76,7 +76,6 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { // fast path with a single syscall for systems with poll() #[cfg(not(any( - miri, // no `poll` target_os = "emscripten", target_os = "fuchsia", target_os = "vxworks", diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index a5304dc3ef781..fe774105a99ef 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -227,7 +227,19 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt { } } -impl FileDescription for io::Stdin { +#[derive(Debug)] +struct Stdin { + stdin: io::Stdin, + watched: ReadinessWatched, +} + +impl Stdin { + fn new() -> Self { + Self { stdin: io::stdin(), watched: ReadinessWatched::default() } + } +} + +impl FileDescription for Stdin { fn name(&self) -> &'static str { "stdin" } @@ -245,17 +257,39 @@ impl FileDescription for io::Stdin { helpers::isolation_abort_error("`read` from stdin")?; } - let mut stdin = &*self; - let result = ecx.read_from_host(|buf| stdin.read(buf), len, ptr)?; + let result = ecx.read_from_host(|buf| (&mut &self.stdin).read(buf), len, ptr)?; finish.call(ecx, result) } fn is_tty(&self, communicate_allowed: bool) -> bool { - communicate_allowed && self.is_terminal() + communicate_allowed && self.stdin.is_terminal() + } + + fn readiness_watched(&self) -> Option<&ReadinessWatched> { + Some(&self.watched) } + + fn readiness(&self) -> Readiness { + // stdin can always be read (we never return EWOULDBLOCK there) and never be written. + let mut readiness = Readiness::EMPTY; + readiness.readable = true; + readiness + } +} + +#[derive(Debug)] +struct Stdout { + stdout: io::Stdout, + watched: ReadinessWatched, } -impl FileDescription for io::Stdout { +impl Stdout { + fn new() -> Self { + Self { stdout: io::stdout(), watched: ReadinessWatched::default() } + } +} + +impl FileDescription for Stdout { fn name(&self) -> &'static str { "stdout" } @@ -269,7 +303,7 @@ impl FileDescription for io::Stdout { finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { // We allow writing to stdout even with isolation enabled. - let result = ecx.write_to_host(&*self, len, ptr)?; + let result = ecx.write_to_host(&self.stdout, len, ptr)?; // Stdout is buffered, flush to make sure it appears on the // screen. This is the write() syscall of the interpreted // program, we want it to correspond to a write() syscall on @@ -281,11 +315,34 @@ impl FileDescription for io::Stdout { } fn is_tty(&self, communicate_allowed: bool) -> bool { - communicate_allowed && self.is_terminal() + communicate_allowed && self.stdout.is_terminal() + } + + fn readiness_watched(&self) -> Option<&ReadinessWatched> { + Some(&self.watched) } + + fn readiness(&self) -> Readiness { + // stdout can always be written (we never return EWOULDBLOCK there) and never be read. + let mut readiness = Readiness::EMPTY; + readiness.writable = true; + readiness + } +} + +#[derive(Debug)] +struct Stderr { + stderr: io::Stderr, + watched: ReadinessWatched, } -impl FileDescription for io::Stderr { +impl Stderr { + fn new() -> Self { + Self { stderr: io::stderr(), watched: ReadinessWatched::default() } + } +} + +impl FileDescription for Stderr { fn name(&self) -> &'static str { "stderr" } @@ -299,13 +356,65 @@ impl FileDescription for io::Stderr { finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { // We allow writing to stderr even with isolation enabled. - let result = ecx.write_to_host(&*self, len, ptr)?; + let result = ecx.write_to_host(&self.stderr, len, ptr)?; // No need to flush, stderr is not buffered. finish.call(ecx, result) } fn is_tty(&self, communicate_allowed: bool) -> bool { - communicate_allowed && self.is_terminal() + communicate_allowed && self.stderr.is_terminal() + } + + fn readiness_watched(&self) -> Option<&ReadinessWatched> { + Some(&self.watched) + } + + fn readiness(&self) -> Readiness { + // stderr can always be written (we never return EWOULDBLOCK there) and never be read. + let mut readiness = Readiness::EMPTY; + readiness.writable = true; + readiness + } +} + +/// Like /dev/null +#[derive(Debug)] +pub struct NullOutput { + watched: ReadinessWatched, +} + +impl NullOutput { + fn new() -> Self { + Self { watched: ReadinessWatched::default() } + } +} + +impl FileDescription for NullOutput { + fn name(&self) -> &'static str { + "null output" + } + + fn write<'tcx>( + self: FileDescriptionRef, + _communicate_allowed: bool, + _ptr: Pointer, + len: usize, + ecx: &mut MiriInterpCx<'tcx>, + finish: DynMachineCallback<'tcx, Result>, + ) -> InterpResult<'tcx> { + // We just don't write anything, but report to the user that we did. + finish.call(ecx, Ok(len)) + } + + fn readiness_watched(&self) -> Option<&ReadinessWatched> { + Some(&self.watched) + } + + fn readiness(&self) -> Readiness { + // null output can always be written (we never return EWOULDBLOCK there) and never be read. + let mut readiness = Readiness::EMPTY; + readiness.writable = true; + readiness } } @@ -416,28 +525,6 @@ impl FileDescription for DirHandle { } } -/// Like /dev/null -#[derive(Debug)] -pub struct NullOutput; - -impl FileDescription for NullOutput { - fn name(&self) -> &'static str { - "stderr and stdout" - } - - fn write<'tcx>( - self: FileDescriptionRef, - _communicate_allowed: bool, - _ptr: Pointer, - len: usize, - ecx: &mut MiriInterpCx<'tcx>, - finish: DynMachineCallback<'tcx, Result>, - ) -> InterpResult<'tcx> { - // We just don't write anything, but report to the user that we did. - finish.call(ecx, Ok(len)) - } -} - /// Internal type of a file-descriptor - this is what [`FdTable`] expects pub type FdNum = i32; @@ -461,13 +548,13 @@ impl FdTable { } pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable { let mut fds = FdTable::new(); - fds.insert_new(io::stdin()); + fds.insert_new(Stdin::new()); if mute_stdout_stderr { - assert_eq!(fds.insert_new(NullOutput), 1); - assert_eq!(fds.insert_new(NullOutput), 2); + assert_eq!(fds.insert_new(NullOutput::new()), 1); + assert_eq!(fds.insert_new(NullOutput::new()), 2); } else { - assert_eq!(fds.insert_new(io::stdout()), 1); - assert_eq!(fds.insert_new(io::stderr()), 2); + assert_eq!(fds.insert_new(Stdout::new()), 1); + assert_eq!(fds.insert_new(Stderr::new()), 2); } fds } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-poll-std-handles.rs b/src/tools/miri/tests/pass-dep/libc/libc-poll-std-handles.rs new file mode 100644 index 0000000000000..401cc0e35dab7 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-poll-std-handles.rs @@ -0,0 +1,21 @@ +//! Ensure we can poll the std handles, both the normal ones and the "null" ones. +//@revisions: normal null +//@[null]compile-flags: -Zmiri-mute-stdout-stderr + +#[path = "../../utils/libc.rs"] +mod libc_utils; +use libc_utils::*; + +fn main() { + let pfds: &mut [_] = &mut [ + libc::pollfd { fd: 0, events: libc::POLLOUT | libc::POLLIN, revents: 0 }, + libc::pollfd { fd: 1, events: libc::POLLOUT | libc::POLLIN, revents: 0 }, + libc::pollfd { fd: 2, events: libc::POLLOUT | libc::POLLIN, revents: 0 }, + ]; + let num = errno_result(unsafe { libc::poll(pfds.as_mut_ptr(), 3, 0) }).unwrap(); + assert_eq!(num, 3); + + assert_eq!(pfds[0].revents, libc::POLLIN); + assert_eq!(pfds[1].revents, libc::POLLOUT); + assert_eq!(pfds[2].revents, libc::POLLOUT); +} From 8bf37cecf6b22b3bc14d2a9d4800a0475d6dce7a Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 26 Jul 2026 12:58:08 +0200 Subject: [PATCH 16/28] Fix decoding attributes of `SyntheticCoroutineBody` --- compiler/rustc_metadata/src/rmeta/decoder.rs | 29 +++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 91252ae6727ce..560d39403d4e0 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -1432,17 +1432,26 @@ impl CrateMetadata { .attributes .get(self, id) .unwrap_or_else(|| { - // Structure and variant constructors don't have any attributes encoded for them, - // but we assume that someone passing a constructor ID actually wants to look at - // the attributes on the corresponding struct or variant. let def_key = self.def_key(id); - assert_eq!(def_key.disambiguated_data.data, DefPathData::Ctor); - let parent_id = def_key.parent.expect("no parent for a constructor"); - self.root - .tables - .attributes - .get(self, parent_id) - .expect("no encoded attributes for a structure or variant") + match def_key.disambiguated_data.data { + DefPathData::Ctor => { + // Structure and variant constructors don't have any attributes encoded for them, + // but we assume that someone passing a constructor ID actually wants to look at + // the attributes on the corresponding struct or variant. + assert_eq!(def_key.disambiguated_data.data, DefPathData::Ctor); + let parent_id = def_key.parent.expect("no parent for a constructor"); + self.root + .tables + .attributes + .get(self, parent_id) + .expect("no encoded attributes for a structure or variant") + } + DefPathData::SyntheticCoroutineBody => { + // SyntheticCoroutineBodies cannot have attributes + LazyArray::default() + } + _ => panic!("Definition key {def_key:?} of type `{:?}` did not have any attributes stored", def_key.disambiguated_data.data) + } }) .decode((self, tcx)) } From e12d28fadffc6496109f17ed3f6f95f8878da25a Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 26 Jul 2026 13:12:38 +0200 Subject: [PATCH 17/28] Add regression test Co-authored-by: Dominik Schwaiger --- src/tools/miri/tests/deps/Cargo.lock | 8 ++++++ src/tools/miri/tests/deps/Cargo.toml | 2 ++ .../tests/deps/cross-crate-items/Cargo.toml | 7 +++++ .../tests/deps/cross-crate-items/src/lib.rs | 5 ++++ .../pass-dep/cross_crate_async_closure_ice.rs | 26 +++++++++++++++++++ 5 files changed, 48 insertions(+) create mode 100644 src/tools/miri/tests/deps/cross-crate-items/Cargo.toml create mode 100644 src/tools/miri/tests/deps/cross-crate-items/src/lib.rs create mode 100644 src/tools/miri/tests/pass-dep/cross_crate_async_closure_ice.rs diff --git a/src/tools/miri/tests/deps/Cargo.lock b/src/tools/miri/tests/deps/Cargo.lock index 4691588eefb40..c59b729e5eeca 100644 --- a/src/tools/miri/tests/deps/Cargo.lock +++ b/src/tools/miri/tests/deps/Cargo.lock @@ -32,6 +32,13 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cross-crate-items" +version = "0.1.0" +dependencies = [ + "futures", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -290,6 +297,7 @@ dependencies = [ name = "miri-test-deps" version = "0.1.0" dependencies = [ + "cross-crate-items", "futures", "getrandom 0.1.16", "getrandom 0.2.17", diff --git a/src/tools/miri/tests/deps/Cargo.toml b/src/tools/miri/tests/deps/Cargo.toml index bbbcc316f31c2..a377141f98aaf 100644 --- a/src/tools/miri/tests/deps/Cargo.toml +++ b/src/tools/miri/tests/deps/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] # all dependencies (and their transitive ones) listed here can be used in `tests/*-dep`. +cross-crate-items = { path = "cross-crate-items" } # Used to test things that need multiple crates to check libc = "0.2" num_cpus = "1.10.1" @@ -35,3 +36,4 @@ windows-sys = { version = "0.61", features = [ # Make sure we are not part of the rustc workspace. [workspace] +members = ["cross-crate-items"] diff --git a/src/tools/miri/tests/deps/cross-crate-items/Cargo.toml b/src/tools/miri/tests/deps/cross-crate-items/Cargo.toml new file mode 100644 index 0000000000000..1f9fc93cd5237 --- /dev/null +++ b/src/tools/miri/tests/deps/cross-crate-items/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "cross-crate-items" +version = "0.1.0" +edition = "2021" + +[dependencies] +futures = { version = "0.3.0", default-features = false, features = ["alloc", "async-await"] } diff --git a/src/tools/miri/tests/deps/cross-crate-items/src/lib.rs b/src/tools/miri/tests/deps/cross-crate-items/src/lib.rs new file mode 100644 index 0000000000000..8f462d5b515f1 --- /dev/null +++ b/src/tools/miri/tests/deps/cross-crate-items/src/lib.rs @@ -0,0 +1,5 @@ +/// Async closure in an external crate — required because decoder.rs is only called for +/// defs loaded from .rmeta files, not local crate defs. +pub fn returns_async_closure() -> impl futures::Sink<(), Error = std::io::Error> { + futures::sink::unfold((), async |(), ()| Ok::<_, std::io::Error>(())) +} diff --git a/src/tools/miri/tests/pass-dep/cross_crate_async_closure_ice.rs b/src/tools/miri/tests/pass-dep/cross_crate_async_closure_ice.rs new file mode 100644 index 0000000000000..d09ff7c77c9a4 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/cross_crate_async_closure_ice.rs @@ -0,0 +1,26 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes + +// This is a regression test for a Miri ICE when encountering a `SyntheticCoroutineBody` from an external crate (see +// `cross_crate_items` for details). Calling the async closure triggered the ICE: https://github.com/rust-lang/rust/issues/156905 + +use std::future::Future; +use std::task::{Context, Poll, Waker}; + +use futures::SinkExt as _; + +// Taken from tests/pass/async-fn.rs. +fn run_fut(fut: impl Future) -> T { + let mut context = Context::from_waker(Waker::noop()); + let mut pinned = Box::pin(fut); + loop { + match pinned.as_mut().poll(&mut context) { + Poll::Pending => continue, + Poll::Ready(v) => return v, + } + } +} + +fn main() { + let mut sink = Box::pin(cross_crate_items::returns_async_closure()); + run_fut(sink.send(())).unwrap(); +} From a4caad86a0e4c6290f6c0e1dc444bbc43e265653 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 26 Jul 2026 13:01:15 +0200 Subject: [PATCH 18/28] add missing direct test for fcntl(F_GETFD) --- src/tools/miri/src/shims/files.rs | 5 ++++- src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 8 ++++++++ .../miri/tests/pass-dep/libc/libc-poll-std-handles.rs | 4 +++- src/tools/miri/tests/pass-dep/libc/libc-socket.rs | 2 +- src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs | 2 +- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index fe774105a99ef..6726b85e03403 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -257,6 +257,7 @@ impl FileDescription for Stdin { helpers::isolation_abort_error("`read` from stdin")?; } + // FIXME: this can block on the host, halting the entire interpreter. let result = ecx.read_from_host(|buf| (&mut &self.stdin).read(buf), len, ptr)?; finish.call(ecx, result) } @@ -270,9 +271,11 @@ impl FileDescription for Stdin { } fn readiness(&self) -> Readiness { - // stdin can always be read (we never return EWOULDBLOCK there) and never be written. + // Stdin is readable (we never return EWOULDBLOCK above) and also writable (since that never + // blocks either). This matches what we see on Linux. let mut readiness = Readiness::EMPTY; readiness.readable = true; + readiness.writable = true; readiness } } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 09d3c6b857204..3c11d0f38e17a 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -21,6 +21,7 @@ use libc_utils::{errno_check, errno_result}; fn main() { test_dup(); test_dup_stdout_stderr(); + test_fcntl_getfd(); test_canonicalize_too_long(); test_rename(); test_ftruncate::(libc::ftruncate); @@ -310,6 +311,13 @@ fn test_dup() { } } +fn test_fcntl_getfd() { + // This should succeed for FDs that exist and fail for those that do not. + let _success = errno_result(unsafe { libc::fcntl(0, libc::F_GETFD) }).unwrap(); + let err = errno_result(unsafe { libc::fcntl(1337, libc::F_GETFD) }).unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::EBADF); +} + fn test_canonicalize_too_long() { // Make sure we get an error for long paths. let too_long = "x/".repeat(libc::PATH_MAX.try_into().unwrap()); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-poll-std-handles.rs b/src/tools/miri/tests/pass-dep/libc/libc-poll-std-handles.rs index 401cc0e35dab7..bcff187859546 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-poll-std-handles.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-poll-std-handles.rs @@ -1,6 +1,8 @@ //! Ensure we can poll the std handles, both the normal ones and the "null" ones. +//@ignore-target: windows # no libc //@revisions: normal null //@[null]compile-flags: -Zmiri-mute-stdout-stderr +//@run-native #[path = "../../utils/libc.rs"] mod libc_utils; @@ -15,7 +17,7 @@ fn main() { let num = errno_result(unsafe { libc::poll(pfds.as_mut_ptr(), 3, 0) }).unwrap(); assert_eq!(num, 3); - assert_eq!(pfds[0].revents, libc::POLLIN); + assert_eq!(pfds[0].revents, libc::POLLIN | libc::POLLOUT); assert_eq!(pfds[1].revents, libc::POLLOUT); assert_eq!(pfds[2].revents, libc::POLLOUT); } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs index 3a4ece46c9b61..85d6302a0175d 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs @@ -908,7 +908,7 @@ fn test_sockopt_rcvtimeo() { /// the operation is finished, even when the socket file _descriptor_ gets /// closed in the mean time. fn test_unblock_after_socket_close() { - // MacOS behaves different (`read` errors with EBADFD when the file description is closed) + // MacOS behaves different (`read` errors with EBADF when the file description is closed) // so we skip the test when we are run on a native macOS target. if cfg!(not(miri)) && cfg!(target_os = "macos") { return; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs index 461a209fbe10b..916aeed5232d7 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs @@ -185,7 +185,7 @@ fn test_blocking_write() { /// the operation is finished, even when the socket file _descriptor_ gets /// closed in the mean time. fn test_unblock_after_socket_close() { - // MacOS behaves different (`read` errors with EBADFD when the file description is closed) + // MacOS behaves different (`read` errors with EBADF when the file description is closed) // so we skip the test when we are run on a native macOS target. if cfg!(not(miri)) && cfg!(target_os = "macos") { return; From 8b1803ad5db682b5e7d710a575bc5ba40d9baace Mon Sep 17 00:00:00 2001 From: asuto15 Date: Tue, 14 Jul 2026 06:19:32 +0900 Subject: [PATCH 19/28] Add suggestions for using `#[export_name]` instead of `#[link_name]` on statics --- .../src/session_diagnostics.rs | 12 +++++++++ .../rustc_attr_parsing/src/target_checking.rs | 14 ++++++++++- tests/ui/attributes/link-name-on-static.rs | 13 ++++++++++ .../ui/attributes/link-name-on-static.stderr | 17 +++++++++++++ .../attributes/unsafe-link-name-on-static.rs | 7 ++++++ .../unsafe-link-name-on-static.stderr | 25 +++++++++++++++++++ 6 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 tests/ui/attributes/link-name-on-static.rs create mode 100644 tests/ui/attributes/link-name-on-static.stderr create mode 100644 tests/ui/attributes/unsafe-link-name-on-static.rs create mode 100644 tests/ui/attributes/unsafe-link-name-on-static.stderr diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index fb984912b1936..b913518c191db 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -403,6 +403,18 @@ pub(crate) struct InvalidTarget { #[derive(Subdiagnostic)] pub(crate) enum InvalidTargetHelp { + #[multipart_suggestion( + "did you mean to use `#[export_name]`?", + applicability = "maybe-incorrect" + )] + UseExportName { + #[suggestion_part(code = "unsafe(")] + unsafe_open: Option, + #[suggestion_part(code = "export_name")] + name: Span, + #[suggestion_part(code = ")")] + unsafe_close: Option, + }, #[help("use `#[rustc_align(...)]` instead")] UseRustcAlign, #[help("use `#[rustc_align_static(...)]` instead")] diff --git a/compiler/rustc_attr_parsing/src/target_checking.rs b/compiler/rustc_attr_parsing/src/target_checking.rs index 461820626f2ad..f2d25d5a960ef 100644 --- a/compiler/rustc_attr_parsing/src/target_checking.rs +++ b/compiler/rustc_attr_parsing/src/target_checking.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use rustc_ast::AttrStyle; +use rustc_ast::{AttrStyle, Safety}; use rustc_errors::{DiagArgValue, MultiSpan, StashKey}; use rustc_feature::Features; use rustc_hir::attrs::AttributeKind; @@ -187,6 +187,18 @@ impl<'sess> AttributeParser<'sess> { cx: &AcceptContext<'_, '_>, ) -> Option { match &*cx.attr_path.segments { + [sym::link_name] + if cx.target == Target::Static + && matches!(cx.attr_safety, Safety::Default | Safety::Unsafe(_)) => + { + Some(InvalidTargetHelp::UseExportName { + unsafe_open: matches!(cx.attr_safety, Safety::Default) + .then(|| cx.inner_span.shrink_to_lo()), + name: cx.attr_path.span, + unsafe_close: matches!(cx.attr_safety, Safety::Default) + .then(|| cx.inner_span.shrink_to_hi()), + }) + } [sym::repr] if attribute_args == "(align(...))" => match cx.target { Target::Fn | Target::Method(..) if cx.features().fn_align() => { Some(InvalidTargetHelp::UseRustcAlign) diff --git a/tests/ui/attributes/link-name-on-static.rs b/tests/ui/attributes/link-name-on-static.rs new file mode 100644 index 0000000000000..be56852ad5fdf --- /dev/null +++ b/tests/ui/attributes/link-name-on-static.rs @@ -0,0 +1,13 @@ +//@ check-pass + +#[link_name = "VALUE"] +//~^ WARN `#[link_name]` attribute cannot be used on statics +//~| WARN this was previously accepted by the compiler but is being phased out +static VALUE_DEFINITION: u8 = 0; + +unsafe extern "C" { + #[link_name = "VALUE"] + static VALUE_DECLARATION: u8; +} + +fn main() {} diff --git a/tests/ui/attributes/link-name-on-static.stderr b/tests/ui/attributes/link-name-on-static.stderr new file mode 100644 index 0000000000000..c08b9ee23721b --- /dev/null +++ b/tests/ui/attributes/link-name-on-static.stderr @@ -0,0 +1,17 @@ +warning: `#[link_name]` attribute cannot be used on statics + --> $DIR/link-name-on-static.rs:3:1 + | +LL | #[link_name = "VALUE"] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: `#[link_name]` can be applied to foreign functions and foreign statics + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: requested on the command line with `-W unused-attributes` +help: did you mean to use `#[export_name]`? + | +LL - #[link_name = "VALUE"] +LL + #[unsafe(export_name = "VALUE")] + | + +warning: 1 warning emitted + diff --git a/tests/ui/attributes/unsafe-link-name-on-static.rs b/tests/ui/attributes/unsafe-link-name-on-static.rs new file mode 100644 index 0000000000000..1c88a89829485 --- /dev/null +++ b/tests/ui/attributes/unsafe-link-name-on-static.rs @@ -0,0 +1,7 @@ +#[unsafe(link_name = "VALUE")] +//~^ ERROR `link_name` is not an unsafe attribute +//~| WARN `#[link_name]` attribute cannot be used on statics +//~| WARN this was previously accepted by the compiler but is being phased out +static VALUE_DEFINITION: u8 = 0; + +fn main() {} diff --git a/tests/ui/attributes/unsafe-link-name-on-static.stderr b/tests/ui/attributes/unsafe-link-name-on-static.stderr new file mode 100644 index 0000000000000..5d987f7df390c --- /dev/null +++ b/tests/ui/attributes/unsafe-link-name-on-static.stderr @@ -0,0 +1,25 @@ +error: `link_name` is not an unsafe attribute + --> $DIR/unsafe-link-name-on-static.rs:1:3 + | +LL | #[unsafe(link_name = "VALUE")] + | ^^^^^^ this is not an unsafe attribute + | + = note: extraneous unsafe is not allowed in attributes + +warning: `#[link_name]` attribute cannot be used on statics + --> $DIR/unsafe-link-name-on-static.rs:1:1 + | +LL | #[unsafe(link_name = "VALUE")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: `#[link_name]` can be applied to foreign functions and foreign statics + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: requested on the command line with `-W unused-attributes` +help: did you mean to use `#[export_name]`? + | +LL - #[unsafe(link_name = "VALUE")] +LL + #[unsafe(export_name = "VALUE")] + | + +error: aborting due to 1 previous error; 1 warning emitted + From 5e5367ba4d58d8551aef91017f6ea95d3e8a95be Mon Sep 17 00:00:00 2001 From: asuto15 Date: Sun, 26 Jul 2026 20:30:27 +0900 Subject: [PATCH 20/28] Remove redundant suggestion filter and consolidate UI tests --- .../rustc_attr_parsing/src/target_checking.rs | 13 +++----- tests/ui/attributes/link-name-on-static.rs | 10 ++++-- .../ui/attributes/link-name-on-static.stderr | 32 ++++++++++++++++--- .../attributes/unsafe-link-name-on-static.rs | 7 ---- .../unsafe-link-name-on-static.stderr | 25 --------------- 5 files changed, 39 insertions(+), 48 deletions(-) delete mode 100644 tests/ui/attributes/unsafe-link-name-on-static.rs delete mode 100644 tests/ui/attributes/unsafe-link-name-on-static.stderr diff --git a/compiler/rustc_attr_parsing/src/target_checking.rs b/compiler/rustc_attr_parsing/src/target_checking.rs index f2d25d5a960ef..6b0d1b094ca0f 100644 --- a/compiler/rustc_attr_parsing/src/target_checking.rs +++ b/compiler/rustc_attr_parsing/src/target_checking.rs @@ -187,16 +187,13 @@ impl<'sess> AttributeParser<'sess> { cx: &AcceptContext<'_, '_>, ) -> Option { match &*cx.attr_path.segments { - [sym::link_name] - if cx.target == Target::Static - && matches!(cx.attr_safety, Safety::Default | Safety::Unsafe(_)) => - { + [sym::link_name] if cx.target == Target::Static => { + let needs_unsafe_wrapper = matches!(cx.attr_safety, Safety::Default); + Some(InvalidTargetHelp::UseExportName { - unsafe_open: matches!(cx.attr_safety, Safety::Default) - .then(|| cx.inner_span.shrink_to_lo()), + unsafe_open: needs_unsafe_wrapper.then(|| cx.inner_span.shrink_to_lo()), name: cx.attr_path.span, - unsafe_close: matches!(cx.attr_safety, Safety::Default) - .then(|| cx.inner_span.shrink_to_hi()), + unsafe_close: needs_unsafe_wrapper.then(|| cx.inner_span.shrink_to_hi()), }) } [sym::repr] if attribute_args == "(align(...))" => match cx.target { diff --git a/tests/ui/attributes/link-name-on-static.rs b/tests/ui/attributes/link-name-on-static.rs index be56852ad5fdf..85082d3a43fb4 100644 --- a/tests/ui/attributes/link-name-on-static.rs +++ b/tests/ui/attributes/link-name-on-static.rs @@ -1,10 +1,14 @@ -//@ check-pass - #[link_name = "VALUE"] -//~^ WARN `#[link_name]` attribute cannot be used on statics +//~^ WARN the `link_name` attribute cannot be used on statics //~| WARN this was previously accepted by the compiler but is being phased out static VALUE_DEFINITION: u8 = 0; +#[unsafe(link_name = "UNSAFE_VALUE")] +//~^ ERROR `link_name` is not an unsafe attribute +//~| WARN the `link_name` attribute cannot be used on statics +//~| WARN this was previously accepted by the compiler but is being phased out +static UNSAFE_VALUE_DEFINITION: u8 = 0; + unsafe extern "C" { #[link_name = "VALUE"] static VALUE_DECLARATION: u8; diff --git a/tests/ui/attributes/link-name-on-static.stderr b/tests/ui/attributes/link-name-on-static.stderr index c08b9ee23721b..2d768acd51bfb 100644 --- a/tests/ui/attributes/link-name-on-static.stderr +++ b/tests/ui/attributes/link-name-on-static.stderr @@ -1,10 +1,18 @@ -warning: `#[link_name]` attribute cannot be used on statics - --> $DIR/link-name-on-static.rs:3:1 +error: `link_name` is not an unsafe attribute + --> $DIR/link-name-on-static.rs:6:3 + | +LL | #[unsafe(link_name = "UNSAFE_VALUE")] + | ^^^^^^ this is not an unsafe attribute + | + = note: extraneous unsafe is not allowed in attributes + +warning: the `link_name` attribute cannot be used on statics + --> $DIR/link-name-on-static.rs:1:3 | LL | #[link_name = "VALUE"] - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^ | - = help: `#[link_name]` can be applied to foreign functions and foreign statics + = help: the `link_name` attribute can be applied to foreign functions and foreign statics = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: requested on the command line with `-W unused-attributes` help: did you mean to use `#[export_name]`? @@ -13,5 +21,19 @@ LL - #[link_name = "VALUE"] LL + #[unsafe(export_name = "VALUE")] | -warning: 1 warning emitted +warning: the `link_name` attribute cannot be used on statics + --> $DIR/link-name-on-static.rs:6:10 + | +LL | #[unsafe(link_name = "UNSAFE_VALUE")] + | ^^^^^^^^^ + | + = help: the `link_name` attribute can be applied to foreign functions and foreign statics + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! +help: did you mean to use `#[export_name]`? + | +LL - #[unsafe(link_name = "UNSAFE_VALUE")] +LL + #[unsafe(export_name = "UNSAFE_VALUE")] + | + +error: aborting due to 1 previous error; 2 warnings emitted diff --git a/tests/ui/attributes/unsafe-link-name-on-static.rs b/tests/ui/attributes/unsafe-link-name-on-static.rs deleted file mode 100644 index 1c88a89829485..0000000000000 --- a/tests/ui/attributes/unsafe-link-name-on-static.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[unsafe(link_name = "VALUE")] -//~^ ERROR `link_name` is not an unsafe attribute -//~| WARN `#[link_name]` attribute cannot be used on statics -//~| WARN this was previously accepted by the compiler but is being phased out -static VALUE_DEFINITION: u8 = 0; - -fn main() {} diff --git a/tests/ui/attributes/unsafe-link-name-on-static.stderr b/tests/ui/attributes/unsafe-link-name-on-static.stderr deleted file mode 100644 index 5d987f7df390c..0000000000000 --- a/tests/ui/attributes/unsafe-link-name-on-static.stderr +++ /dev/null @@ -1,25 +0,0 @@ -error: `link_name` is not an unsafe attribute - --> $DIR/unsafe-link-name-on-static.rs:1:3 - | -LL | #[unsafe(link_name = "VALUE")] - | ^^^^^^ this is not an unsafe attribute - | - = note: extraneous unsafe is not allowed in attributes - -warning: `#[link_name]` attribute cannot be used on statics - --> $DIR/unsafe-link-name-on-static.rs:1:1 - | -LL | #[unsafe(link_name = "VALUE")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: `#[link_name]` can be applied to foreign functions and foreign statics - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: requested on the command line with `-W unused-attributes` -help: did you mean to use `#[export_name]`? - | -LL - #[unsafe(link_name = "VALUE")] -LL + #[unsafe(export_name = "VALUE")] - | - -error: aborting due to 1 previous error; 1 warning emitted - From 91cead3cb081f34661ef940b5a911a6e3ba7bbcf Mon Sep 17 00:00:00 2001 From: Peter Szilvasi Date: Fri, 24 Jul 2026 08:29:56 +0000 Subject: [PATCH 21/28] Update expect message using the recommended style in binary_heap module --- library/alloc/src/collections/binary_heap/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/alloc/src/collections/binary_heap/mod.rs b/library/alloc/src/collections/binary_heap/mod.rs index 34de563fe4f33..98192e1fb5b01 100644 --- a/library/alloc/src/collections/binary_heap/mod.rs +++ b/library/alloc/src/collections/binary_heap/mod.rs @@ -1258,7 +1258,7 @@ impl BinaryHeap { /// /// Ok(heap.pop()) /// } - /// # find_max_slow(&[1, 2, 3]).expect("why is the test harness OOMing on 12 bytes?"); + /// # find_max_slow(&[1, 2, 3]).expect("reserving capacity for 12 bytes should never fail"); /// ``` #[stable(feature = "try_reserve_2", since = "1.63.0")] pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> { @@ -1294,7 +1294,7 @@ impl BinaryHeap { /// /// Ok(heap.pop()) /// } - /// # find_max_slow(&[1, 2, 3]).expect("why is the test harness OOMing on 12 bytes?"); + /// # find_max_slow(&[1, 2, 3]).expect("reserving capacity for 12 bytes should never fail"); /// ``` #[stable(feature = "try_reserve_2", since = "1.63.0")] pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { From c46ab28fc3d4876b7be58c2bab8efff05412c07d Mon Sep 17 00:00:00 2001 From: Peter Szilvasi Date: Sun, 26 Jul 2026 13:35:11 +0000 Subject: [PATCH 22/28] Update expect message in library/alloc/src/collections/vec_deque/mod.rs --- library/alloc/src/collections/vec_deque/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index 9eac8bcde1d1c..9ec82c383e6c7 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -1105,7 +1105,7 @@ impl VecDeque { #[stable(feature = "rust1", since = "1.0.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "vecdeque_reserve")] pub fn reserve(&mut self, additional: usize) { - let new_cap = self.len.checked_add(additional).expect("capacity overflow"); + let new_cap = self.len.checked_add(additional).expect("length should not overflow usize"); let old_cap = self.capacity(); if new_cap > old_cap { @@ -1153,7 +1153,7 @@ impl VecDeque { /// /// Ok(output) /// } - /// # process_data(&[1, 2, 3]).expect("why is the test harness OOMing on 12 bytes?"); + /// # process_data(&[1, 2, 3]).expect("reserving capacity for 12 bytes should never fail"); /// ``` #[stable(feature = "try_reserve", since = "1.57.0")] pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> { @@ -1201,7 +1201,7 @@ impl VecDeque { /// /// Ok(output) /// } - /// # process_data(&[1, 2, 3]).expect("why is the test harness OOMing on 12 bytes?"); + /// # process_data(&[1, 2, 3]).expect("reserving capacity for 12 bytes should never fail"); /// ``` #[stable(feature = "try_reserve", since = "1.57.0")] pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { @@ -2732,7 +2732,7 @@ impl VecDeque { #[stable(feature = "append", since = "1.4.0")] pub fn append(&mut self, other: &mut Self) { if T::IS_ZST { - self.len = self.len.checked_add(other.len).expect("capacity overflow"); + self.len = self.len.checked_add(other.len).expect("length should not overflow usize"); other.len = 0; other.head = WrappedIndex::zero(); return; @@ -3871,7 +3871,7 @@ impl Index for VecDeque { #[inline] fn index(&self, index: usize) -> &T { - self.get(index).expect("Out of bounds access") + self.get(index).expect("index should be within bounds") } } @@ -3879,7 +3879,7 @@ impl Index for VecDeque { impl IndexMut for VecDeque { #[inline] fn index_mut(&mut self, index: usize) -> &mut T { - self.get_mut(index).expect("Out of bounds access") + self.get_mut(index).expect("index should be within bounds") } } From b0bc5a0d6db7f5d05f9e26cfd7d159f918477316 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Sun, 26 Jul 2026 15:50:43 +0200 Subject: [PATCH 23/28] rustc_target: callconv: mips64: Return structs with single f128 in FPRs The MIPS n64 ABI returns structs that meet the following requirements: - Up to 128 bits large - Only one or two fields, all of which are floating point - Offset of the first field is zero in floating point registers. This was already accounted for, but the edge case of a struct with a single f128 field was not handled correctly and would always be returned in integer registers. If we tell the backend to use a f128 register here, LLVM will return the value in two of the 64-bit FPRs, $f0 and $f2. That is equivalent to how Clang, GCC and also Rustc today would return a long double / f128. However, both Clang and GCC return a struct with a single long double field in $f0 and $f1. In order to achieve the same result in Rustc, we need to set the InReg attribute on the return value. With this, we now match Clang and GCC. Clang: https://godbolt.org/z/za8qv9P4n GCC: https://godbolt.org/z/9qcdGsce6 Rustc currently: https://godbolt.org/z/8sP5G4ash LLVM IR comparisons: https://godbolt.org/z/ojaTTY9hW --- compiler/rustc_target/src/callconv/mips64.rs | 20 ++- tests/assembly-llvm/mips-struct-float.rs | 151 +++++++++++++++++++ 2 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 tests/assembly-llvm/mips-struct-float.rs diff --git a/compiler/rustc_target/src/callconv/mips64.rs b/compiler/rustc_target/src/callconv/mips64.rs index f17e59c12e57e..a9d5ec958889f 100644 --- a/compiler/rustc_target/src/callconv/mips64.rs +++ b/compiler/rustc_target/src/callconv/mips64.rs @@ -3,7 +3,7 @@ use rustc_abi::{ BackendRepr, FieldsShape, Float, HasDataLayout, Primitive, Reg, Size, TyAbiInterface, }; -use crate::callconv::{ArgAbi, ArgExtension, CastTarget, FnAbi, PassMode, Uniform}; +use crate::callconv::{ArgAbi, ArgAttribute, ArgExtension, CastTarget, FnAbi, PassMode, Uniform}; fn extend_integer_width_mips(arg: &mut ArgAbi<'_, Ty>, bits: u64) { // Always sign extend u32 values on 64-bit mips @@ -27,8 +27,15 @@ where { match ret.layout.field(cx, i).backend_repr { BackendRepr::Scalar(scalar) => match scalar.primitive() { - Primitive::Float(Float::F32) => Some(Reg::f32()), - Primitive::Float(Float::F64) => Some(Reg::f64()), + Primitive::Float(float) => { + match float { + // C does not have the f16 type + Float::F16 => None, + Float::F32 => Some(Reg::f32()), + Float::F64 => Some(Reg::f64()), + Float::F128 => Some(Reg::f128()), + } + } _ => None, }, _ => None, @@ -55,14 +62,17 @@ where if let FieldsShape::Arbitrary { .. } = ret.layout.fields { if ret.layout.fields.count() == 1 { if let Some(reg) = float_reg(cx, ret, 0) { - ret.cast_to(reg); + // The inreg attribute forces LLVM to return a struct containing a f128 in + // $f0 and $f1 rather than $f0 and $f2, see: + // https://github.com/llvm/llvm-project/blob/a81db64570f94c2ca8ac0f598c0b5bba1a7ae59e/llvm/lib/Target/Mips/MipsCallingConv.td#L48-L51 + ret.cast_to_with_attrs(reg, ArgAttribute::InReg.into()); return; } } else if ret.layout.fields.count() == 2 && let Some(reg0) = float_reg(cx, ret, 0) && let Some(reg1) = float_reg(cx, ret, 1) { - ret.cast_to(CastTarget::pair(reg0, reg1)); + ret.cast_to_with_attrs(CastTarget::pair(reg0, reg1), ArgAttribute::InReg.into()); return; } } diff --git a/tests/assembly-llvm/mips-struct-float.rs b/tests/assembly-llvm/mips-struct-float.rs new file mode 100644 index 0000000000000..92f10267e01d2 --- /dev/null +++ b/tests/assembly-llvm/mips-struct-float.rs @@ -0,0 +1,151 @@ +// Tests that MIPS targets return structs that are up to 128 bits large, only +// consist of 1 or 2 floating point fields and have a first field with offset 0 +// in FPRs rather than GPRs. +// +//@ add-minicore +//@ assembly-output: emit-asm +//@ compile-flags: -Copt-level=3 +// +//@ revisions: LE +//@[LE] compile-flags: --target=mips64el-unknown-linux-gnuabi64 +//@[LE] needs-llvm-components: mips +//@ revisions: BE +//@[BE] compile-flags: --target=mips64-unknown-linux-gnuabi64 +//@[BE] needs-llvm-components: mips + +#![crate_type = "lib"] +#![feature(no_core, f128)] +#![no_core] + +extern crate minicore; + +// 128-bit large struct with one floating point field, should be returned in +// $f0 and $f1 to match the de-facto GCC ABI. +#[repr(C)] +struct SingleF128 { + x: f128, +} + +// 64-bit large struct with one floating point field, should be returned in $f0. +#[repr(C)] +struct SingleF64 { + x: f64, +} + +// 32-bit large struct with one floating point field, should be returned in $f0. +#[repr(C)] +struct SingleF32 { + x: f32, +} + +// 128-bit large struct with two floating point fields, should be returned in +// $f0 and $f2. +#[repr(C)] +struct TwoF64 { + x: f64, + y: f64, +} + +// 64-bit large struct with two floating point fields, should be returned in +// $f0 and $f2. +#[repr(C)] +struct TwoF32 { + x: f32, + y: f32, +} + +// 96-bit large struct with two floating point fields, should be returned in +// $f0 and $f2. +#[repr(C)] +struct F32AndF64 { + x: f32, + y: f64, +} + +// 96-bit large struct with two floating-point fields, should be returned in +// $f0 and $f2. +#[repr(C)] +struct F64AndF32 { + x: f64, + y: f32, +} + +// CHECK-LABEL: single_f128 +// CHECK: dmtc1 $4, $f0 +// CHECK-NEXT: jr $ra +// CHECK-NEXT: dmtc1 $5, $f1 +#[unsafe(no_mangle)] +pub extern "C" fn single_f128(a: SingleF128) -> SingleF128 { + a +} + +// CHECK-LABEL: single_f64 +// CHECK: jr $ra +// CHECK-NEXT: mov.d $f0, $f12 +#[unsafe(no_mangle)] +pub extern "C" fn single_f64(a: SingleF64) -> SingleF64 { + a +} + +// CHECK-LABEL: single_f32 +// BE: dsrl $1, $4, 32 +// LE: sll $1, $4, 0 +// BE-NEXT: sll $1, $1, 0 +// CHECK-NEXT: jr $ra +// CHECK-NEXT: mtc1 $1, $f0 +#[unsafe(no_mangle)] +pub extern "C" fn single_f32(a: SingleF32) -> SingleF32 { + a +} + +// CHECK-LABEL: two_f64 +// CHECK: mov.d $f2, $f13 +// CHECK-NEXT: jr $ra +// CHECK-NEXT: mov.d $f0, $f12 +#[unsafe(no_mangle)] +pub extern "C" fn two_f64(a: TwoF64) -> TwoF64 { + a +} + +// CHECK-LABEL: two_f32 +// CHECK: sll $1, $4, 0 +// LE-NEXT: mtc1 $1, $f0 +// BE-NEXT: mtc1 $1, $f2 +// CHECK-NEXT: dsrl $1, $4, 32 +// CHECK-NEXT: sll $1, $1, 0 +// CHECK-NEXT: jr $ra +// LE-NEXT: mtc1 $1, $f2 +// BE-NEXT: mtc1 $1, $f0 +#[unsafe(no_mangle)] +pub extern "C" fn two_f32(a: TwoF32) -> TwoF32 { + a +} + +// We deviate from Clang in the order of instructions in the next two functions +// but that's okay + +// CHECK-LABEL: f32_and_f64 +// BE: dsrl $1, $4, 32 +// LE: mov.d $f2, $f13 +// BE-NEXT: mov.d $f2, $f13 +// LE-NEXT: sll $1, $4, 0 +// BE-NEXT: sll $1, $1, 0 +// CHECK-NEXT: jr $ra +// CHECK-NEXT: mtc1 $1, $f0 +#[unsafe(no_mangle)] +pub extern "C" fn f32_and_f64(a: F32AndF64) -> F32AndF64 { + a +} + +// CHECK-LABEL: f64_and_f32 +// BE: dsrl $1, $5, 32 +// LE: mov.d $f0, $f12 +// BE-NEXT: mov.d $f0, $f12 +// LE-NEXT: sll $1, $5, 0 +// BE-NEXT: sll $1, $1, 0 +// CHECK-NEXT: jr $ra +// CHECK-NEXT: mtc1 $1, $f2 +#[unsafe(no_mangle)] +pub extern "C" fn f64_and_f32(a: F64AndF32) -> F64AndF32 { + a +} From 10a5e9da29cbd79e3673731c978e2e1306f07690 Mon Sep 17 00:00:00 2001 From: Peter Szilvasi Date: Mon, 27 Jul 2026 07:17:16 +0000 Subject: [PATCH 24/28] Update error messages for overflow and out of bound cases --- library/alloc/src/collections/vec_deque/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index 9ec82c383e6c7..940fce7377938 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -1105,7 +1105,7 @@ impl VecDeque { #[stable(feature = "rust1", since = "1.0.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "vecdeque_reserve")] pub fn reserve(&mut self, additional: usize) { - let new_cap = self.len.checked_add(additional).expect("length should not overflow usize"); + let new_cap = self.len.checked_add(additional).expect("capacity overflow"); let old_cap = self.capacity(); if new_cap > old_cap { @@ -2732,7 +2732,7 @@ impl VecDeque { #[stable(feature = "append", since = "1.4.0")] pub fn append(&mut self, other: &mut Self) { if T::IS_ZST { - self.len = self.len.checked_add(other.len).expect("length should not overflow usize"); + self.len = self.len.checked_add(other.len).expect("capacity overflow"); other.len = 0; other.head = WrappedIndex::zero(); return; @@ -3871,7 +3871,7 @@ impl Index for VecDeque { #[inline] fn index(&self, index: usize) -> &T { - self.get(index).expect("index should be within bounds") + self.get(index).expect("out of bounds access") } } @@ -3879,7 +3879,7 @@ impl Index for VecDeque { impl IndexMut for VecDeque { #[inline] fn index_mut(&mut self, index: usize) -> &mut T { - self.get_mut(index).expect("index should be within bounds") + self.get_mut(index).expect("out of bounds access") } } From 7c8c136f0fe1809616efe1a84f6c6e333a31daee Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Fri, 26 Jun 2026 05:15:11 +0900 Subject: [PATCH 25/28] add regression test for cfg_eval derive reparse recovery --- .../cfg-eval-derive-invalid-reparse-no-ice.rs | 17 +++++++++++ ...-eval-derive-invalid-reparse-no-ice.stderr | 28 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.rs create mode 100644 tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.stderr diff --git a/tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.rs b/tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.rs new file mode 100644 index 0000000000000..1f0215afadc7b --- /dev/null +++ b/tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.rs @@ -0,0 +1,17 @@ +// Regression test for https://github.com/rust-lang/rust/issues/148891. + +macro_rules! values { + ($inner:ty) => { + #[derive(Debug)] + pub enum TokenKind { + #[cfg(test)] + STRING([u8; $inner]), + //~^ ERROR expected expression, found `ty` metavariable + //~| ERROR macro expansion ignores `)` and any tokens following + } + }; +} + +values!(String); + +fn main() {} diff --git a/tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.stderr b/tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.stderr new file mode 100644 index 0000000000000..0248b6636fb7a --- /dev/null +++ b/tests/ui/cfg/cfg-eval-derive-invalid-reparse-no-ice.stderr @@ -0,0 +1,28 @@ +error: expected expression, found `ty` metavariable + --> $DIR/cfg-eval-derive-invalid-reparse-no-ice.rs:8:25 + | +LL | pub enum TokenKind { + | --------- while parsing this enum +LL | #[cfg(test)] +LL | STRING([u8; $inner]), + | ^^^^^^ expected expression +... +LL | values!(String); + | --------------- in this macro invocation + | + = help: enum variants can be `Variant`, `Variant = `, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }` + = note: this error originates in the macro `values` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: macro expansion ignores `)` and any tokens following + --> $DIR/cfg-eval-derive-invalid-reparse-no-ice.rs:8:32 + | +LL | STRING([u8; $inner]), + | ^ +... +LL | values!(String); + | --------------- caused by the macro expansion here + | + = note: the usage of `values!` is likely invalid in item context + +error: aborting due to 2 previous errors + From 3f883706060c2b8736df50106ddccc5005cab65e Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Fri, 26 Jun 2026 05:15:31 +0900 Subject: [PATCH 26/28] avoid ICE when cfg_eval reparse recovers no annotatable --- compiler/rustc_builtin_macros/src/cfg_eval.rs | 36 ++++++++++--------- tests/crashes/148891.rs | 14 -------- 2 files changed, 19 insertions(+), 31 deletions(-) delete mode 100644 tests/crashes/148891.rs diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs index 2fb89939181e6..34ddd9427cdde 100644 --- a/compiler/rustc_builtin_macros/src/cfg_eval.rs +++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs @@ -107,40 +107,42 @@ impl CfgEval<'_> { // our attribute target will correctly configure the tokens as well. let mut parser = Parser::new(&self.0.sess.psess, orig_tokens, None); parser.capture_cfg = true; - let res: PResult<'_, Annotatable> = try { - match annotatable { - Annotatable::Item(_) => { - let item = - parser.parse_item(ForceCollect::Yes, AllowConstBlockItems::Yes)?.unwrap(); - Annotatable::Item(self.flat_map_item(item).pop().unwrap()) - } + let res: PResult<'_, Option> = try { + match &annotatable { + Annotatable::Item(_) => parser + .parse_item(ForceCollect::Yes, AllowConstBlockItems::Yes)? + .and_then(|item| self.flat_map_item(item).pop().map(Annotatable::Item)), Annotatable::AssocItem(_, ctxt) => { - let item = parser.parse_trait_item(ForceCollect::Yes)?.unwrap().unwrap(); - Annotatable::AssocItem( - self.flat_map_assoc_item(item, ctxt).pop().unwrap(), - ctxt, - ) + parser.parse_trait_item(ForceCollect::Yes)?.flatten().and_then(|item| { + self.flat_map_assoc_item(item, *ctxt) + .pop() + .map(|item| Annotatable::AssocItem(item, *ctxt)) + }) } Annotatable::ForeignItem(_) => { - let item = parser.parse_foreign_item(ForceCollect::Yes)?.unwrap().unwrap(); - Annotatable::ForeignItem(self.flat_map_foreign_item(item).pop().unwrap()) + parser.parse_foreign_item(ForceCollect::Yes)?.flatten().and_then(|item| { + self.flat_map_foreign_item(item).pop().map(Annotatable::ForeignItem) + }) } Annotatable::Stmt(_) => { let stmt = parser.parse_stmt_without_recovery(false, ForceCollect::Yes, false)?; - Annotatable::Stmt(Box::new(self.flat_map_stmt(stmt).pop().unwrap())) + self.flat_map_stmt(stmt).pop().map(|stmt| Annotatable::Stmt(Box::new(stmt))) } Annotatable::Expr(_) => { let mut expr = parser.parse_expr_force_collect()?; self.visit_expr(&mut expr); - Annotatable::Expr(expr) + Some(Annotatable::Expr(expr)) } _ => unreachable!(), } }; match res { - Ok(ann) => ann, + Ok(Some(ann)) => ann, + // Parser recovery may emit errors without reconstructing an annotatable. + // Keep the original node so cfg-eval stays best-effort instead of ICEing. + Ok(None) => annotatable, Err(err) => { err.emit(); annotatable diff --git a/tests/crashes/148891.rs b/tests/crashes/148891.rs deleted file mode 100644 index 75534440d4902..0000000000000 --- a/tests/crashes/148891.rs +++ /dev/null @@ -1,14 +0,0 @@ -//@ known-bug: #148891 -macro_rules! values { - ($inner:ty) => { - #[derive(Debug)] - pub enum TokenKind { - #[cfg(test)] - STRING ([u8; $inner]), - } - }; -} - -values!(String); - -pub fn main() {} From 0ad2a3b3c2df974e9f32959f92da99bc18264454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 27 Jul 2026 07:52:45 +0000 Subject: [PATCH 27/28] Detect when a macro without exclamation mark uses square brackets ``` error: expected one of `.`, `?`, `]`, or an operator, found `,` --> $DIR/do-not-suggest-semicolon-between-macro-without-exclamation-mark-and-array.rs:2:19 | LL | let _x = vec[1, 2, 3]; | ^ expected one of `.`, `?`, `]`, or an operator | help: you might have meant to call a macro | LL | let _x = vec![1, 2, 3]; | + ``` --- compiler/rustc_parse/src/parser/expr.rs | 16 +++++++++++++--- ...cro-without-exclamation-mark-and-array.stderr | 5 +++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 94d3c7dccfc6c..34044e72ab92b 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1380,12 +1380,22 @@ impl<'a> Parser<'a> { /// Parse an indexing expression `expr[...]`. fn parse_expr_index(&mut self, lo: Span, base: Box) -> PResult<'a, Box> { - let prev_span = self.prev_token.span; + let prev_token = self.prev_token; let open_delim_span = self.token.span; self.bump(); // `[` let index = self.parse_expr()?; - self.suggest_missing_semicolon_before_array(prev_span, open_delim_span)?; - self.expect(exp!(CloseBracket))?; + self.suggest_missing_semicolon_before_array(prev_token.span, open_delim_span)?; + self.expect(exp!(CloseBracket)).map_err(|mut e| { + if let TokenKind::Ident(_, _) = prev_token.kind { + e.span_suggestion_verbose( + prev_token.span.shrink_to_hi(), + "you might have meant to call a macro", + "!".to_string(), + Applicability::MaybeIncorrect, + ); + } + e + })?; Ok(self.mk_expr( lo.to(self.prev_token.span), self.mk_index(base, index, open_delim_span.to(self.prev_token.span)), diff --git a/tests/ui/parser/macro/do-not-suggest-semicolon-between-macro-without-exclamation-mark-and-array.stderr b/tests/ui/parser/macro/do-not-suggest-semicolon-between-macro-without-exclamation-mark-and-array.stderr index 2796312f4ad17..548c142732edf 100644 --- a/tests/ui/parser/macro/do-not-suggest-semicolon-between-macro-without-exclamation-mark-and-array.stderr +++ b/tests/ui/parser/macro/do-not-suggest-semicolon-between-macro-without-exclamation-mark-and-array.stderr @@ -3,6 +3,11 @@ error: expected one of `.`, `?`, `]`, or an operator, found `,` | LL | let _x = vec[1, 2, 3]; | ^ expected one of `.`, `?`, `]`, or an operator + | +help: you might have meant to call a macro + | +LL | let _x = vec![1, 2, 3]; + | + error: aborting due to 1 previous error From c707fad015da683152b3771ae7ca156f3d9ff52c Mon Sep 17 00:00:00 2001 From: Sang Woo Kim Date: Mon, 27 Jul 2026 17:54:04 +0900 Subject: [PATCH 28/28] std: make send_process_group_signal unsupported on VxWorks VxWorks has no process groups: the SDK defines no `killpg`, and `getpgrp`/`setpgid` are no-ops in libunix, so `kill(-pgrp, sig)` is no substitute either. libc cannot usefully declare `killpg` for this target, so calling it leaves the target unable to build std. Panic instead, matching what fuchsia does for the same method. --- library/std/src/sys/process/unix/vxworks.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/library/std/src/sys/process/unix/vxworks.rs b/library/std/src/sys/process/unix/vxworks.rs index 33e7f6a9a0fea..18b9782f40230 100644 --- a/library/std/src/sys/process/unix/vxworks.rs +++ b/library/std/src/sys/process/unix/vxworks.rs @@ -171,12 +171,9 @@ impl Process { } } - pub fn send_process_group_signal(&self, signal: i32) -> io::Result<()> { - // See note in `send_signal` regarding recycled PIDs. - if self.status.is_some() { - return Ok(()); - } - cvt(unsafe { libc::killpg(self.pid, signal) }).map(drop) + pub fn send_process_group_signal(&self, _signal: i32) -> io::Result<()> { + // VxWorks doesn't have process groups + unimplemented!() } pub fn wait(&mut self) -> io::Result {