From 36e4fe5b5e73e24f0df6397151db16608d9f6de2 Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Fri, 12 Jun 2026 22:03:43 -0700 Subject: [PATCH 01/57] reject extern statics in promotion --- compiler/rustc_mir_transform/src/promote_consts.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/rustc_mir_transform/src/promote_consts.rs b/compiler/rustc_mir_transform/src/promote_consts.rs index 3694a0614a7b7..96057ad413802 100644 --- a/compiler/rustc_mir_transform/src/promote_consts.rs +++ b/compiler/rustc_mir_transform/src/promote_consts.rs @@ -317,6 +317,8 @@ impl<'tcx> Validator<'_, 'tcx> { // can only promote static accesses inside statics. && let Some(hir::ConstContext::Static(..)) = self.const_kind && !self.tcx.is_thread_local_static(did) + // Extern statics can never be read by CTFE, even inside a static. + && !self.tcx.is_foreign_item(did) { // Recurse. } else { From 9ac260686c2042fbff28381d69db5f6bc23fb3e6 Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Fri, 12 Jun 2026 22:03:56 -0700 Subject: [PATCH 02/57] re-bless tests --- ...0].SimplifyCfg-pre-optimizations.after.mir | 18 -------- ...motion_extern_static.FOO.PromoteTemps.diff | 44 ------------------- .../mir-opt/const_promotion_extern_static.rs | 7 --- .../extern-static-in-static-ice-143174.rs | 15 +++++++ .../extern-static-in-static-ice-143174.stderr | 18 ++++++++ .../extern-static-promotion-rejected.rs | 11 +++++ .../extern-static-promotion-rejected.stderr | 8 ++++ 7 files changed, 52 insertions(+), 69 deletions(-) delete mode 100644 tests/mir-opt/const_promotion_extern_static.FOO-promoted[0].SimplifyCfg-pre-optimizations.after.mir delete mode 100644 tests/mir-opt/const_promotion_extern_static.FOO.PromoteTemps.diff create mode 100644 tests/ui/statics/extern-static-in-static-ice-143174.rs create mode 100644 tests/ui/statics/extern-static-in-static-ice-143174.stderr create mode 100644 tests/ui/statics/extern-static-promotion-rejected.rs create mode 100644 tests/ui/statics/extern-static-promotion-rejected.stderr diff --git a/tests/mir-opt/const_promotion_extern_static.FOO-promoted[0].SimplifyCfg-pre-optimizations.after.mir b/tests/mir-opt/const_promotion_extern_static.FOO-promoted[0].SimplifyCfg-pre-optimizations.after.mir deleted file mode 100644 index 72cb64e275e36..0000000000000 --- a/tests/mir-opt/const_promotion_extern_static.FOO-promoted[0].SimplifyCfg-pre-optimizations.after.mir +++ /dev/null @@ -1,18 +0,0 @@ -// MIR for `FOO::promoted[0]` after SimplifyCfg-pre-optimizations - -const FOO::promoted[0]: &[&i32; 1] = { - let mut _0: &[&i32; 1]; - let mut _1: [&i32; 1]; - let mut _2: &i32; - let mut _3: *const i32; - - bb0: { - _3 = const {ALLOC0: *const i32}; - _2 = &(*_3); - _1 = [move _2]; - _0 = &_1; - return; - } -} - -ALLOC0 (extern static: X) diff --git a/tests/mir-opt/const_promotion_extern_static.FOO.PromoteTemps.diff b/tests/mir-opt/const_promotion_extern_static.FOO.PromoteTemps.diff deleted file mode 100644 index 0e4eed2c028d0..0000000000000 --- a/tests/mir-opt/const_promotion_extern_static.FOO.PromoteTemps.diff +++ /dev/null @@ -1,44 +0,0 @@ -- // MIR for `FOO` before PromoteTemps -+ // MIR for `FOO` after PromoteTemps - - static mut FOO: *const &i32 = { - let mut _0: *const &i32; - let mut _1: &[&i32]; - let mut _2: &[&i32; 1]; - let _3: [&i32; 1]; - let mut _4: &i32; - let _5: *const i32; -+ let mut _6: &[&i32; 1]; - - bb0: { - StorageLive(_1); - StorageLive(_2); -- StorageLive(_3); -- StorageLive(_4); -- StorageLive(_5); -- _5 = const {ALLOC0: *const i32}; -- _4 = &(*_5); -- _3 = [move _4]; -- _2 = &_3; -+ _6 = const FOO::promoted[0]; -+ _2 = &(*_6); - _1 = move _2 as &[&i32] (PointerCoercion(Unsize, Implicit)); -- StorageDead(_4); - StorageDead(_2); - _0 = core::slice::::as_ptr(move _1) -> [return: bb1, unwind: bb2]; - } - - bb1: { -- StorageDead(_5); -- StorageDead(_3); - StorageDead(_1); - return; - } - - bb2 (cleanup): { - resume; - } - } -- -- ALLOC0 (extern static: X) - diff --git a/tests/mir-opt/const_promotion_extern_static.rs b/tests/mir-opt/const_promotion_extern_static.rs index f16a53270a97d..ec9368094a752 100644 --- a/tests/mir-opt/const_promotion_extern_static.rs +++ b/tests/mir-opt/const_promotion_extern_static.rs @@ -1,18 +1,11 @@ //@ skip-filecheck //@ ignore-endian-big -extern "C" { - static X: i32; -} static Y: i32 = 42; // EMIT_MIR const_promotion_extern_static.BAR.PromoteTemps.diff // EMIT_MIR const_promotion_extern_static.BAR-promoted[0].SimplifyCfg-pre-optimizations.after.mir static mut BAR: *const &i32 = [&Y].as_ptr(); -// EMIT_MIR const_promotion_extern_static.FOO.PromoteTemps.diff -// EMIT_MIR const_promotion_extern_static.FOO-promoted[0].SimplifyCfg-pre-optimizations.after.mir -static mut FOO: *const &i32 = [unsafe { &X }].as_ptr(); - // EMIT_MIR const_promotion_extern_static.BOP.built.after.mir static BOP: &i32 = &13; diff --git a/tests/ui/statics/extern-static-in-static-ice-143174.rs b/tests/ui/statics/extern-static-in-static-ice-143174.rs new file mode 100644 index 0000000000000..868d9bab73663 --- /dev/null +++ b/tests/ui/statics/extern-static-in-static-ice-143174.rs @@ -0,0 +1,15 @@ +// Regression test for #143174. + +#![crate_type = "lib"] + +type Fun = unsafe extern "C" fn(); + +struct Foo(Fun); + +static FOO: &Foo = &Foo(BAR); +//~^ ERROR cannot access extern static `BAR` [E0080] +//~| ERROR use of extern static is unsafe and requires unsafe function or block [E0133] + +unsafe extern "C" { + static BAR: Fun; +} diff --git a/tests/ui/statics/extern-static-in-static-ice-143174.stderr b/tests/ui/statics/extern-static-in-static-ice-143174.stderr new file mode 100644 index 0000000000000..f38968031ba66 --- /dev/null +++ b/tests/ui/statics/extern-static-in-static-ice-143174.stderr @@ -0,0 +1,18 @@ +error[E0080]: cannot access extern static `BAR` + --> $DIR/extern-static-in-static-ice-143174.rs:9:25 + | +LL | static FOO: &Foo = &Foo(BAR); + | ^^^ evaluation of `FOO` failed here + +error[E0133]: use of extern static is unsafe and requires unsafe function or block + --> $DIR/extern-static-in-static-ice-143174.rs:9:25 + | +LL | static FOO: &Foo = &Foo(BAR); + | ^^^ use of extern static + | + = note: extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0080, E0133. +For more information about an error, try `rustc --explain E0080`. diff --git a/tests/ui/statics/extern-static-promotion-rejected.rs b/tests/ui/statics/extern-static-promotion-rejected.rs new file mode 100644 index 0000000000000..f024f00490f74 --- /dev/null +++ b/tests/ui/statics/extern-static-promotion-rejected.rs @@ -0,0 +1,11 @@ +// previously part of tests/mir-opt/const_promotion_extern_static.rs +// promotion of extern statics is now rejected entirely, even if we're not trying to read its value + +unsafe extern "C" { + static X: i32; +} + +static mut FOO: *const &i32 = [unsafe { &X }].as_ptr(); +//~^ ERROR dangling pointer + +fn main() {} diff --git a/tests/ui/statics/extern-static-promotion-rejected.stderr b/tests/ui/statics/extern-static-promotion-rejected.stderr new file mode 100644 index 0000000000000..52e24a0bd942c --- /dev/null +++ b/tests/ui/statics/extern-static-promotion-rejected.stderr @@ -0,0 +1,8 @@ +error: encountered dangling pointer in final value of mutable static + --> $DIR/extern-static-promotion-rejected.rs:8:1 + | +LL | static mut FOO: *const &i32 = [unsafe { &X }].as_ptr(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 0439f4ef09af50b440af245a0f2173442fbcd567 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:32:21 +0100 Subject: [PATCH 03/57] Hint that memchr returns an in-bounds index --- library/core/src/slice/memchr.rs | 7 ++++++- library/coretests/tests/slice.rs | 11 +++++++++++ .../codegen-llvm/lib-optimizations/memchr-result.rs | 13 +++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/codegen-llvm/lib-optimizations/memchr-result.rs diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 1e1053583a617..6762015181d85 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -28,7 +28,12 @@ pub const fn memchr(x: u8, text: &[u8]) -> Option { return memchr_naive(x, text); } - memchr_aligned(x, text) + let result = memchr_aligned(x, text); + if let Some(index) = result { + // SAFETY: `memchr_aligned` only returns the index of a matching byte in `text`. + unsafe { crate::hint::assert_unchecked(index < text.len()) }; + } + result } #[inline] diff --git a/library/coretests/tests/slice.rs b/library/coretests/tests/slice.rs index a4db7304fff90..b05f54d4df0a2 100644 --- a/library/coretests/tests/slice.rs +++ b/library/coretests/tests/slice.rs @@ -1781,6 +1781,17 @@ pub mod memchr { assert_eq!(None, memchr(b'a', b"xyz")); } + #[test] + fn each_alignment() { + let mut data = [1u8; 64]; + let needle = 2; + let pos = 40; + data[pos] = needle; + for start in 0..16 { + assert_eq!(Some(pos - start), memchr(needle, &data[start..])); + } + } + #[test] fn matches_one_reversed() { assert_eq!(Some(0), memrchr(b'a', b"a")); diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs new file mode 100644 index 0000000000000..fbdbdcc3fe9f3 --- /dev/null +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -0,0 +1,13 @@ +// Ensure `memchr` communicates that a returned index is in bounds. +//@ compile-flags: -Copt-level=3 -Zinline-mir=false +//@ only-64bit + +#![crate_type = "lib"] + +// CHECK-LABEL: @find_char +#[no_mangle] +pub fn find_char(haystack: &str, needle: char) -> Option { + // CHECK-NOT: phi { i64, i64 } + // CHECK: ret { i64, i64 } + haystack.find(needle) +} From c1f36d5f4bde0f955e9d0cbdec406d22b5044360 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:29:23 +0100 Subject: [PATCH 04/57] Hint that memrchr returns an in-bounds index --- library/core/src/slice/memchr.rs | 10 ++++++++++ .../codegen-llvm/lib-optimizations/memchr-result.rs | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 6762015181d85..c83e8b218da08 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -112,8 +112,18 @@ const fn memchr_aligned(x: u8, text: &[u8]) -> Option { } /// Returns the last index matching the byte `x` in `text`. +#[inline] #[must_use] pub fn memrchr(x: u8, text: &[u8]) -> Option { + let result = memrchr_aligned(x, text); + if let Some(index) = result { + // SAFETY: `memrchr_aligned` only returns the index of a matching byte in `text`. + unsafe { crate::hint::assert_unchecked(index < text.len()) }; + } + result +} + +fn memrchr_aligned(x: u8, text: &[u8]) -> Option { // Scan for a single byte value by reading two `usize` words at a time. // // Split `text` in three parts: diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs index fbdbdcc3fe9f3..f18335075451c 100644 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -3,6 +3,11 @@ //@ only-64bit #![crate_type = "lib"] +#![feature(slice_internals)] + +extern crate core; + +use core::slice::memchr::memrchr; // CHECK-LABEL: @find_char #[no_mangle] @@ -11,3 +16,11 @@ pub fn find_char(haystack: &str, needle: char) -> Option { // CHECK: ret { i64, i64 } haystack.find(needle) } + +// CHECK-LABEL: @rfind_byte +#[no_mangle] +pub fn rfind_byte(haystack: &[u8], needle: u8) -> Option { + // CHECK-NOT: panic_bounds_check + // CHECK: ret { i1, i8 } + memrchr(needle, haystack).map(|index| haystack[index]) +} From 844c01e43be5782643646d73a6f65539db046a33 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:14:14 +0000 Subject: [PATCH 05/57] Cover memchr fast path with bounds assertion --- library/core/src/slice/memchr.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index c83e8b218da08..017661f0448c2 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,13 +24,13 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - if text.len() < 2 * USIZE_BYTES { - return memchr_naive(x, text); - } - - let result = memchr_aligned(x, text); + let result = if text.len() < 2 * USIZE_BYTES { + memchr_naive(x, text) + } else { + memchr_aligned(x, text) + }; if let Some(index) = result { - // SAFETY: `memchr_aligned` only returns the index of a matching byte in `text`. + // SAFETY: Both implementations only return an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; } result From 49c1f02279a37b85fcd9448dc7b87e20923f57dd Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:32:44 +0000 Subject: [PATCH 06/57] Fix memchr result CI checks --- library/core/src/slice/memchr.rs | 7 ++----- tests/codegen-llvm/lib-optimizations/memchr-result.rs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 017661f0448c2..fb99e86139d7e 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,11 +24,8 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - let result = if text.len() < 2 * USIZE_BYTES { - memchr_naive(x, text) - } else { - memchr_aligned(x, text) - }; + let result = + if text.len() < 2 * USIZE_BYTES { memchr_naive(x, text) } else { memchr_aligned(x, text) }; if let Some(index) = result { // SAFETY: Both implementations only return an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs index f18335075451c..77abc33adde83 100644 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -1,6 +1,6 @@ // Ensure `memchr` communicates that a returned index is in bounds. //@ compile-flags: -Copt-level=3 -Zinline-mir=false -//@ only-64bit +//@ only-x86_64 #![crate_type = "lib"] #![feature(slice_internals)] From 19fe04b2d3e1b58815534e648202889b1eff56b0 Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:18:27 +0330 Subject: [PATCH 07/57] Add regression test for GAT bound mismatched-type error Proving `T::Assoc<_>: Sized` while a where-clause bound `T::Assoc: Sized` was in scope used to over-eagerly infer the unconstrained argument to `u8`, causing a spurious "mismatched types" error. It should compile; lock that in. --- .../gat-bound-mismatch-106832.rs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/ui/generic-associated-types/gat-bound-mismatch-106832.rs diff --git a/tests/ui/generic-associated-types/gat-bound-mismatch-106832.rs b/tests/ui/generic-associated-types/gat-bound-mismatch-106832.rs new file mode 100644 index 0000000000000..943bc3b32b4b1 --- /dev/null +++ b/tests/ui/generic-associated-types/gat-bound-mismatch-106832.rs @@ -0,0 +1,30 @@ +//! Regression test for . +//! +//! Proving `T::Assoc<_>: Sized` while a where-clause bound `T::Assoc: Sized` is +//! in scope used to over-eagerly infer the otherwise-unconstrained argument to `u8`, +//! producing a spurious "mismatched types" error. This should compile. + +//@ check-pass + +#![allow(dead_code)] + +trait Trait { + type Assoc; +} + +fn test() +where + T::Assoc: Sized, +{ + // `_` must be inferred from the `1i32` argument, not eagerly unified with `u8` + // just because `T::Assoc: Sized` happens to be in the environment. + constrain::(1i32); +} + +fn constrain(_: A) +where + T::Assoc: Sized, +{ +} + +fn main() {} From 807750a1fcdb31f4bf527089ff44cf95ac199046 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Tue, 28 Jul 2026 20:57:45 +0000 Subject: [PATCH 08/57] Preserve memchr codegen on LLVM 21 --- library/core/src/slice/memchr.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index fb99e86139d7e..68826ecac31f3 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,10 +24,18 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - let result = - if text.len() < 2 * USIZE_BYTES { memchr_naive(x, text) } else { memchr_aligned(x, text) }; + if text.len() < 2 * USIZE_BYTES { + let result = memchr_naive(x, text); + if let Some(index) = result { + // SAFETY: `memchr_naive` only returns an index from within `text`. + unsafe { crate::hint::assert_unchecked(index < text.len()) }; + } + return result; + } + + let result = memchr_aligned(x, text); if let Some(index) = result { - // SAFETY: Both implementations only return an index from within `text`. + // SAFETY: `memchr_aligned` only returns an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; } result From 1e1aad2b026e15776d7e764cc6da97969ba1ddc5 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 21 Jul 2026 13:05:02 +1000 Subject: [PATCH 09/57] Inline the splatted_callee function --- compiler/rustc_mir_build/src/thir/cx/expr.rs | 136 +++++++++---------- 1 file changed, 61 insertions(+), 75 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 4b067a8ca79e2..badce1d168138 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -1224,85 +1224,32 @@ impl<'tcx> ThirBuildCx<'tcx> { } } - fn splatted_callee( - &mut self, - expr: &hir::Expr<'_>, - span: Span, - ) -> (Expr<'tcx>, u16 /* arg_index */, u16 /* arg_count */) { - let SplattedDef { def_id, arg_index, arg_count } = - self.typeck_results.splatted_def(expr.hir_id).unwrap_or_else(|| { - span_bug!(expr.span, "no splatted def for function or method callee") - }); - - let expr = if let Some(def_id) = def_id { - // We're calling a function via a FnDef, and its possibly generic type - let def_kind = self.tcx.def_kind(def_id); - let user_ty = self.user_args_applied_to_res(expr.hir_id, Res::Def(def_kind, def_id)); - debug!( - "splatted_callee FnDef: user_ty={:?} def_kind={:?} def_id={:?} arg_index={:?} arg_count={:?}", - user_ty, def_kind, def_id, arg_index, arg_count, - ); - - Expr { - temp_scope_id: expr.hir_id.local_id, - ty: self - .tcx - .type_of(def_id) - .instantiate(self.tcx, self.typeck_results.node_args(expr.hir_id)) - .skip_norm_wip(), - span, - kind: ExprKind::ZstLiteral { user_ty }, - } - } else { - // We're calling a function via a FnPtr and its type - // FIXME(splat): populate the side-tables for FnPtrs, using liberated_fn_sigs if needed - let fn_ty = self.typeck_results.expr_ty_adjusted(expr); - let user_ty = - self.typeck_results.user_provided_types().get(expr.hir_id).copied().map(Box::new); - debug!( - "splatted_callee FnPtr: user_ty={:?} fn_ty={:?} arg_index={:?} arg_count={:?}", - user_ty, fn_ty, arg_index, arg_count, - ); - - if !fn_ty.is_fn() { - span_bug!(expr.span, "splatted FnPtr side-tables are not yet implemented") - } - - Expr { - temp_scope_id: expr.hir_id.local_id, - // Create a new FnPtr FnSig type, representing the splatted function arguments with - // user-supplied generic types applied - ty: Ty::new_fn_ptr(self.tcx, fn_ty.fn_sig(self.tcx)), - span, - kind: ExprKind::ZstLiteral { user_ty }, - } - }; - - (expr, arg_index, arg_count) - } - /// The callee has a splatted tuple argument. /// Rewrite a splatted call `receiver.f(a, u, v)` into `receiver.f(a, #[rustc_splat] (u, v))`. /// The receiver is optional. fn convert_splatted_callee( &mut self, - expr: &hir::Expr<'_>, + call_expr: &'tcx hir::Expr<'_>, fn_span: Span, args: &'tcx [hir::Expr<'tcx>], receiver: Option<&'tcx hir::Expr<'tcx>>, ) -> ExprKind<'tcx> { let tcx = self.tcx; - // The callee has a splatted tuple argument. - let (func, tupled_arg_index, tupled_args_count) = self.splatted_callee(expr, fn_span); - let tupled_arg_index = usize::from(tupled_arg_index); - let tupled_args_count = usize::from(tupled_args_count); + // Look up the typeck results + let splatted_def = + self.typeck_results.splatted_def(call_expr.hir_id).unwrap_or_else(|| { + span_bug!(call_expr.span, "no splatted def for function or method callee") + }); + + let tupled_arg_index = usize::from(splatted_def.arg_index); + let tupled_args_count = usize::from(splatted_def.arg_count); // Splatting an empty tuple is permitted: `a.f() -> Trait::f(a, #[rustc_splat] ())`. // In that case, the tupled arg index is one past the end of the args. if tupled_arg_index + tupled_args_count > args.len() { span_bug!( - expr.span, + call_expr.span, "splatted arg index out of bounds of function args: {:?} + {:?} > {:?} for function call: receiver {:?}, args {:?}", tupled_arg_index, tupled_args_count, @@ -1312,7 +1259,7 @@ impl<'tcx> ThirBuildCx<'tcx> { ); } - info!("Using splatted function span: {:?}", func.span); + debug!("Using splatted function span: {:?}", fn_span); // Split into non-tupled and tupled arguments let initial_non_tupled_args = @@ -1331,29 +1278,68 @@ impl<'tcx> ThirBuildCx<'tcx> { let tupled_arg_tys = tupled_args.iter().map(|e| self.typeck_results.expr_ty_adjusted(e)); - let temp_scope_id = - if receiver.is_some() { func.temp_scope_id } else { expr.hir_id.local_id }; + // We need the tupled arguments in HIR/MIR for type checking + // FIXME(splat): de-tuple args in codegen for performance let tupled_args = Expr { ty: Ty::new_tup_from_iter(tcx, tupled_arg_tys), - temp_scope_id, - span: expr.span, + temp_scope_id: call_expr.hir_id.local_id, + span: call_expr.span, kind: ExprKind::Tuple { fields: self.mirror_exprs(tupled_args) }, }; let tupled_args = self.thir.exprs.push(tupled_args); - let mut args = - if let Some(receiver) = receiver { vec![self.mirror_expr(receiver)] } else { vec![] }; + // Handle the receiver as the first arg, if present + let mut args = Vec::with_capacity( + usize::from(receiver.is_some()) + + initial_non_tupled_args.len() + + 1 + + final_non_tupled_args.len(), + ); + if let Some(receiver) = receiver { + args.push(self.mirror_expr(receiver)); + } args.extend(initial_non_tupled_args); args.push(tupled_args); args.extend(final_non_tupled_args); - // We need the tupled arguments in HIR/MIR for type checking, but codegen can - // de-tuple them for performance - let fn_span = if receiver.is_some() { func.span } else { expr.span }; + let fn_span = if receiver.is_some() { fn_span } else { call_expr.span }; + + let (fn_ty, fun_expr) = match (splatted_def, receiver) { + // Create a FnDef shim for user-provided types + (SplattedDef { def_id: Some(def_id), arg_index, arg_count }, _) => { + // We're calling a function via a FnDef, and its possibly generic type + // This is effectively `self.method_callee(call_expr, fn_span, None)`, + // applied to `splatted_def` instead of `type_dependent_def`. + let def_kind = self.tcx.def_kind(def_id); + let user_ty = + self.user_args_applied_to_res(call_expr.hir_id, Res::Def(def_kind, def_id)); + debug!( + "splatted_callee FnDef: user_ty={:?} def_kind={:?} def_id={:?} arg_index={:?} arg_count={:?}", + user_ty, def_kind, def_id, arg_index, arg_count, + ); + + // Create a new FnDef expression with user-provided type applied + let callee_expr = Expr { + temp_scope_id: call_expr.hir_id.local_id, + ty: self + .tcx + .type_of(def_id) + .instantiate(self.tcx, self.typeck_results.node_args(call_expr.hir_id)) + .skip_norm_wip(), + span: fn_span, + kind: ExprKind::ZstLiteral { user_ty }, + }; + (callee_expr.ty, self.thir.exprs.push(callee_expr)) + } + (SplattedDef { def_id: None, .. }, _) => { + span_bug!(call_expr.span, "splatted FnPtr side-tables are not yet implemented"); + } + }; + ExprKind::Call { - ty: func.ty, - fun: self.thir.exprs.push(func), + ty: fn_ty, + fun: fun_expr, args: args.into_boxed_slice(), from_hir_call: true, fn_span, From 9f9fb37f39e7813bb8a90bbaf5cfbc486fbab5cf Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 28 Jul 2026 14:24:06 +1000 Subject: [PATCH 10/57] Refactor splat using custom enums (with stubs) --- compiler/rustc_hir_typeck/src/callee.rs | 26 +++++-- compiler/rustc_hir_typeck/src/expr.rs | 11 +-- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 42 ++++++++---- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 68 +++++++++++-------- .../rustc_middle/src/ty/typeck_results.rs | 68 +++++++++++++++---- compiler/rustc_mir_build/src/thir/cx/expr.rs | 66 ++++++++++++++---- 6 files changed, 204 insertions(+), 77 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 288a1903bf675..0caac74f2e4e1 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -31,6 +31,16 @@ use crate::method::TreatNotYetDefinedOpaques; use crate::method::confirm::ConfirmContext; use crate::method::probe::{IsSuggestion, Mode}; +/// Side-table info for lowering splatted function arguments. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) enum SplatLoweringInfo<'tcx> { + /// The DefId of the FnDef being called, used to look up the function type. + /// Also used during argument suggestion for non-splatted function calls. + FnDef(DefId), + /// FIXME(splat): Stub for non-FnDef + NotAFnDef(std::marker::PhantomData<&'tcx ()>), +} + /// Checks that it is legal to call methods of the trait corresponding /// to `trait_id` (this only cares about the trait, not the specific /// method that is called). @@ -600,13 +610,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); let fn_sig = self.normalize(call_expr.span, Unnormalized::new_wip(fn_sig)); + // Splatted FnDefs use the DefId to look up the type, FnPtrs need it directly + let fn_id = match def_id { + Some(x) => SplatLoweringInfo::FnDef(x), + None => SplatLoweringInfo::NotAFnDef(std::marker::PhantomData), + }; + self.check_argument_types_maybe_method_like( &fn_sig, call_expr, arg_exprs, expected, TupleArgumentsFlag::with_fn_sig_kind(fn_sig.fn_sig_kind, false), - def_id, + fn_id, callee_generic_args, ); @@ -643,7 +659,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs: &'tcx [hir::Expr<'tcx>], expected: Expectation<'tcx>, tuple_arguments_flag: TupleArgumentsFlag, - def_id: Option, + fn_id: SplatLoweringInfo<'tcx>, callee_generic_args: Option>, ) { let do_check = || { @@ -656,7 +672,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs, fn_sig.c_variadic(), tuple_arguments_flag, - def_id, + fn_id, callee_generic_args, ); }; @@ -1074,7 +1090,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs, fn_sig.fn_sig_kind.c_variadic(), TupleArgumentsFlag::rust_fn_trait_call(), - Some(closure_def_id.to_def_id()), + SplatLoweringInfo::FnDef(closure_def_id.to_def_id()), None, ); @@ -1172,7 +1188,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs, method.sig.fn_sig_kind.c_variadic(), TupleArgumentsFlag::rust_fn_trait_call(), - Some(method.def_id), + SplatLoweringInfo::FnDef(method.def_id), None, ); diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 6f9b6a4f14ce9..0e2720f4aa1d1 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -39,6 +39,7 @@ use rustc_trait_selection::traits::{self, ObligationCauseCode, ObligationCtxt}; use tracing::{debug, instrument, trace}; use crate::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectation}; +use crate::callee::SplatLoweringInfo; use crate::coercion::CoerceMany; use crate::diagnostics::{ AddressOfTemporaryTaken, BaseExpressionDoubleDot, BaseExpressionDoubleDotAddExpr, @@ -1487,7 +1488,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { args, method.sig.fn_sig_kind.c_variadic(), method_tuple_args_flag, - Some(method.def_id), + SplatLoweringInfo::FnDef(method.def_id), Some(method.args), ); @@ -1499,22 +1500,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let guar = self.report_method_error(expr.hir_id, rcvr_t, error, expected, false); let err_inputs = self.err_args(args.len(), guar); - let err_output = Ty::new_error(self.tcx, guar); + let err_ty = Ty::new_error(self.tcx, guar); self.check_argument_types( segment.ident.span, expr, &err_inputs, - err_output, + err_ty, NoExpectation, args, false, TupleArgumentsFlag::DontTupleArguments, - None, + SplatLoweringInfo::NotAFnDef(std::marker::PhantomData), Some(GenericArgsRef::default()), ); - err_output + err_ty } } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index 1886888c476a0..edfe1ec00f5e7 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -41,7 +41,7 @@ use rustc_trait_selection::traits::{ }; use tracing::{debug, instrument}; -use crate::callee::{self, DeferredCallResolution}; +use crate::callee::{self, DeferredCallResolution, SplatLoweringInfo}; use crate::diagnostics::{self, CtorIsPrivate}; use crate::method::{self, MethodCallee}; use crate::{BreakableCtxt, Diverges, Expectation, FnCtxt, LoweredTy}; @@ -238,7 +238,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(crate) fn write_splatted_resolution( &self, hir_id: HirId, - r: Result, + r: Result, ErrorGuaranteed>, ) { self.typeck_results.borrow_mut().splatted_defs_mut().insert(hir_id, r); } @@ -260,7 +260,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, hir_id: HirId, span: Span, - callee_def_id: Option, + fn_id: SplatLoweringInfo<'tcx>, callee_generic_args: Option>, first_tupled_arg_index: u16, tupled_args_count: u16, @@ -268,16 +268,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // FIXME(const_trait_impl): enforce constness using enforce_context_effects() and add // _and_enforce_effects to this method's name - self.write_splatted_resolution( - hir_id, - Ok(SplattedDef { - def_id: callee_def_id, - arg_index: first_tupled_arg_index, - arg_count: tupled_args_count, - }), - ); - if let Some(callee_generic_args) = callee_generic_args { - self.write_args(hir_id, callee_generic_args); + match fn_id { + // We're splatting a FnDef based on its DefId + SplatLoweringInfo::FnDef(def_id) => { + self.write_splatted_resolution( + hir_id, + Ok(SplattedDef::FnDef { + def_id, + arg_index: first_tupled_arg_index, + arg_count: tupled_args_count, + }), + ); + if let Some(callee_generic_args) = callee_generic_args { + self.write_args(hir_id, callee_generic_args); + } + } + // FIXME(splat): handle FnPtrs + SplatLoweringInfo::NotAFnDef(_) => { + self.write_splatted_resolution( + hir_id, + Ok(SplattedDef::NotAFnDef { + not_yet_implemented: std::marker::PhantomData, + arg_index: first_tupled_arg_index, + arg_count: tupled_args_count, + }), + ); + } } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 949aa32a0b605..cc614542015a2 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -32,6 +32,7 @@ use tracing::debug; use crate::Expectation::*; use crate::TupleArgumentsFlag::*; +use crate::callee::SplatLoweringInfo; use crate::coercion::CoerceMany; use crate::diagnostics::SuggestPtrNullMut; use crate::fn_ctxt::arg_matrix::{ArgMatrix, Compatibility, Error, ExpectedIdx, ProvidedIdx}; @@ -203,8 +204,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { c_variadic: bool, // Whether all the arguments have been bundled in a tuple (ex: closures), or one has been splatted tuple_arguments: TupleArgumentsFlag, - // The DefId for the function being called, for better error messages - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, // The generics of the function being called. Only used for splatting callee_generic_args: Option>, ) { @@ -301,7 +302,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args, expected_input_tys, tuple_arguments, - fn_def_id, + fn_id, callee_generic_args, ); let TupledArgCheckOutcome { @@ -552,7 +553,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -575,8 +576,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { mut expected_input_tys: Option>>, // Whether all the arguments have been bundled in a tuple (ex: closures), or one has been splatted tuple_arguments: TupleArgumentsFlag, - // The DefId for the function being called, for better error messages - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, // The generics of the function being called. Only used for splatting callee_generic_args: Option>, ) -> TupledArgCheckOutcome<'tcx> { @@ -736,7 +737,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If we don't check argument counts here, and there's a subtle bug in the code above, // later compilation stages can fail in unrelated places with confusing errors. if !matches!(tuple_type.kind(), ty::Tuple(_)) { - let spans = if let Some(def_id) = fn_def_id + let spans = if let SplatLoweringInfo::FnDef(def_id) = fn_id && let Some(hir_node) = self.tcx.hir_get_if_local(def_id) && let Some(fn_decl) = hir_node.fn_decl() && let Some(arg_ty) = fn_decl.inputs.get(first_tupled_arg_index_usz) @@ -797,7 +798,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.write_splatted_call( call_expr.hir_id, call_span, - fn_def_id, + fn_id, callee_generic_args, first_tupled_arg_index, tupled_args_count.unwrap().try_into().unwrap(), @@ -834,7 +835,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, // FIXME(splat): when the feature design is settled, improve the errors here @@ -849,7 +851,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -923,7 +925,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Call out where the function is defined fn_call_diag_ctxt.label_fn_like( &mut err, - fn_def_id, + fn_id, fn_call_diag_ctxt.callee_ty, call_expr, None, @@ -1593,7 +1595,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn label_fn_like( &self, err: &mut Diag<'_>, - callable_def_id: Option, + // Lowering info if a splatted function is being called. + callable_id: SplatLoweringInfo<'tcx>, callee_ty: Option>, call_expr: &'tcx hir::Expr<'tcx>, expected_ty: Option>, @@ -1604,7 +1607,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { is_method: bool, tuple_arguments: TupleArgumentsFlag, ) { - let Some(mut def_id) = callable_def_id else { + let SplatLoweringInfo::FnDef(mut def_id) = callable_id else { + // FIXME(FnPtr, splat): Handle FnPtr types and splatting here return; }; @@ -1943,14 +1947,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn label_generic_mismatches( &self, err: &mut Diag<'_>, - callable_def_id: Option, + // Lowering info if a splatted function is being called. + callable_id: SplatLoweringInfo<'tcx>, matched_inputs: &IndexVec>, provided_arg_tys: &IndexVec, Span)>, formal_and_expected_inputs: &IndexVec, Ty<'tcx>)>, is_method: bool, is_splat: bool, ) { - let Some(def_id) = callable_def_id else { + let SplatLoweringInfo::FnDef(def_id) = callable_id else { + // FIXME(FnPtr, splat): Handle FnPtr types and splatting here return; }; @@ -2187,7 +2193,8 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -2199,7 +2206,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -2310,7 +2317,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { }; self.arg_matching_ctxt.args_ctxt.call_ctxt.fn_ctxt.label_fn_like( &mut err, - self.fn_def_id, + self.fn_id, self.callee_ty, self.call_expr, None, @@ -2468,7 +2475,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { // Call out where the function is defined self.label_fn_like( &mut err, - self.fn_def_id, + self.fn_id, self.callee_ty, self.call_expr, Some(expected_ty), @@ -2887,7 +2894,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { fn label_generic_mismatches(&self, err: &mut Diag<'a>) { self.fn_ctxt.label_generic_mismatches( err, - self.fn_def_id, + self.fn_id, &self.matched_inputs, &self.provided_arg_tys, &self.formal_and_expected_inputs, @@ -3082,7 +3089,8 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3094,7 +3102,7 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -3229,7 +3237,8 @@ impl<'a, 'tcx> ArgsCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3241,7 +3250,7 @@ impl<'a, 'tcx> ArgsCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -3348,7 +3357,8 @@ struct CallCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + /// Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3372,7 +3382,8 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3404,7 +3415,7 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -3491,7 +3502,7 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { "()".to_string() } else if ty.is_suggestable(self.tcx, false) { with_forced_trimmed_paths!(format!("/* {ty} */")) - } else if let Some(fn_def_id) = self.fn_def_id + } else if let SplatLoweringInfo::FnDef(fn_def_id) = self.fn_id && self.tcx.def_kind(fn_def_id).is_fn_like() && let self_implicit = matches!(self.call_expr.kind, hir::ExprKind::MethodCall(..)) as usize @@ -3501,6 +3512,7 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { { format!("/* {} */", arg.name) } else { + // FIXME(FnPtr, splat): What suggestions are needed for FnPtrs? "/* value */".to_string() } } diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index a0f38dcb50cb4..cf447eaa5838c 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -37,7 +37,7 @@ pub struct TypeckResults<'tcx> { type_dependent_defs: ItemLocalMap>, /// Resolved definitions for splatted function calls. - splatted_defs: ItemLocalMap>, + splatted_defs: ItemLocalMap, ErrorGuaranteed>>, /// Resolved field indices for field accesses in expressions (`S { field }`, `obj.field`) /// or patterns (`S { field }`). The index is often useful by itself, but to learn more @@ -295,18 +295,20 @@ impl<'tcx> TypeckResults<'tcx> { LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.type_dependent_defs } } - pub fn splatted_defs(&self) -> LocalTableInContext<'_, Result> { + pub fn splatted_defs( + &self, + ) -> LocalTableInContext<'_, Result, ErrorGuaranteed>> { LocalTableInContext { hir_owner: self.hir_owner, data: &self.splatted_defs } } - pub fn splatted_def(&self, id: HirId) -> Option { + pub fn splatted_def(&self, id: HirId) -> Option> { validate_hir_id_for_typeck_results(self.hir_owner, id); self.splatted_defs.get(&id.local_id).cloned().and_then(|r| r.ok()) } pub fn splatted_defs_mut( &mut self, - ) -> LocalTableInContextMut<'_, Result> { + ) -> LocalTableInContextMut<'_, Result, ErrorGuaranteed>> { LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.splatted_defs } } @@ -431,7 +433,7 @@ impl<'tcx> TypeckResults<'tcx> { } pub fn is_splatted_call(&self, expr: &hir::Expr<'_>) -> bool { - matches!(self.splatted_defs().get(expr.hir_id), Some(Ok(SplattedDef { .. }))) + matches!(self.splatted_defs().get(expr.hir_id), Some(Ok(_))) } /// Returns the computed binding mode for a `PatKind::Binding` pattern @@ -598,14 +600,54 @@ impl<'tcx> TypeckResults<'tcx> { /// A resolved splatted function call. #[derive(Debug, Copy, Clone, PartialEq, Eq, StableHash, TyEncodable, TyDecodable)] -pub struct SplattedDef { - /// The function DefId, if available (FnPtrs don't have DefIds) - pub def_id: Option, - /// The index of the first argument in the callee's splatted tuple, and the index of the - /// splatted tuple argument in the caller. - pub arg_index: u16, - /// The number of arguments in the splatted tuple. - pub arg_count: u16, +pub enum SplattedDef<'tcx> { + /// A resolved FnDef call. + FnDef { + /// The DefId of the FnDef (used to look up its type). + def_id: DefId, + + /// The index of the first argument in the callee's splatted tuple, and the index of the + /// splatted tuple argument in the caller. + arg_index: u16, + + /// The number of arguments in the splatted tuple. + arg_count: u16, + }, + + /// FIXME(splat): handle FnPtrs + NotAFnDef { + not_yet_implemented: std::marker::PhantomData<&'tcx ()>, + + /// The index of the first argument in the callee's splatted tuple, and the index of the + /// splatted tuple argument in the caller. + arg_index: u16, + + /// The number of arguments in the splatted tuple. + arg_count: u16, + }, +} + +impl<'tcx> SplattedDef<'tcx> { + pub fn def_id(&self) -> Option { + match self { + SplattedDef::FnDef { def_id, .. } => Some(*def_id), + SplattedDef::NotAFnDef { .. } => None, + } + } + + pub fn arg_index(&self) -> u16 { + match self { + SplattedDef::FnDef { arg_index, .. } => *arg_index, + SplattedDef::NotAFnDef { arg_index, .. } => *arg_index, + } + } + + pub fn arg_count(&self) -> u16 { + match self { + SplattedDef::FnDef { arg_count, .. } => *arg_count, + SplattedDef::NotAFnDef { arg_count, .. } => *arg_count, + } + } } /// Validate that the given HirId (respectively its `local_id` part) can be diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index badce1d168138..0db6cdc2f9ca5 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -28,6 +28,36 @@ use tracing::{debug, info, instrument, trace}; use crate::diagnostics::*; use crate::thir::cx::ThirBuildCx; +/// The receiver of a splatted method, or the expression for a splatted function call. +#[derive(Copy, Clone, Debug)] +enum SplattedFunc<'tcx> { + /// The expression for a method receiver. Always a FnDef. + FnDefReceiver(&'tcx hir::Expr<'tcx>), + /// The expression or path for a function call. + /// This can be a FnDef or FnPtr. + FnExpression(&'tcx hir::Expr<'tcx>), +} + +impl<'tcx> SplattedFunc<'tcx> { + fn has_receiver(&self) -> bool { + matches!(self, SplattedFunc::FnDefReceiver(_)) + } + + fn receiver(&self) -> Option<&'tcx hir::Expr<'tcx>> { + match self { + SplattedFunc::FnDefReceiver(receiver) => Some(receiver), + SplattedFunc::FnExpression(_fn_expression) => None, + } + } + + fn fn_expression(&self) -> Option<&'tcx hir::Expr<'tcx>> { + match self { + SplattedFunc::FnDefReceiver(_receiver) => None, + SplattedFunc::FnExpression(fn_expression) => Some(fn_expression), + } + } +} + fn parsed_attrs(id: HirId, tcx: TyCtxt<'_>) -> ThinVec { HasAttrs::get_attrs(id, &tcx) .into_iter() @@ -375,7 +405,12 @@ impl<'tcx> ThirBuildCx<'tcx> { if self.typeck_results.is_splatted_call(expr) { // The callee has a splatted tuple argument. // rewrite `receiver.f(a, u, v)` into `receiver.f(a, #[rustc_splat] (u, v))` - self.convert_splatted_callee(expr, fn_span, args, Some(receiver)) + self.convert_splatted_callee( + expr, + fn_span, + args, + SplattedFunc::FnDefReceiver(receiver), + ) } else { // Rewrite a.b(c) into UFCS form like Trait::b(a, c) let expr = self.method_callee(expr, segment.ident.span, None); @@ -425,7 +460,12 @@ impl<'tcx> ThirBuildCx<'tcx> { } else if self.typeck_results.is_splatted_call(expr) { // The callee has a splatted tuple argument. // rewrite `f(a, u, v)` into `f(a, #[rustc_splat] (u, v))` - self.convert_splatted_callee(expr, fun.span, args, None) + self.convert_splatted_callee( + expr, + fun.span, + args, + SplattedFunc::FnExpression(fun), + ) } else { // Tuple-like ADTs are represented as ExprKind::Call. We convert them here. let adt_data = if let hir::ExprKind::Path(ref qpath) = fun.kind @@ -1232,7 +1272,7 @@ impl<'tcx> ThirBuildCx<'tcx> { call_expr: &'tcx hir::Expr<'_>, fn_span: Span, args: &'tcx [hir::Expr<'tcx>], - receiver: Option<&'tcx hir::Expr<'tcx>>, + receiver_or_func: SplattedFunc<'tcx>, ) -> ExprKind<'tcx> { let tcx = self.tcx; @@ -1242,19 +1282,19 @@ impl<'tcx> ThirBuildCx<'tcx> { span_bug!(call_expr.span, "no splatted def for function or method callee") }); - let tupled_arg_index = usize::from(splatted_def.arg_index); - let tupled_args_count = usize::from(splatted_def.arg_count); + let tupled_arg_index = usize::from(splatted_def.arg_index()); + let tupled_args_count = usize::from(splatted_def.arg_count()); // Splatting an empty tuple is permitted: `a.f() -> Trait::f(a, #[rustc_splat] ())`. // In that case, the tupled arg index is one past the end of the args. if tupled_arg_index + tupled_args_count > args.len() { span_bug!( call_expr.span, - "splatted arg index out of bounds of function args: {:?} + {:?} > {:?} for function call: receiver {:?}, args {:?}", + "splatted arg index out of bounds of function args: {:?} + {:?} > {:?} for function call: {:?}, args {:?}", tupled_arg_index, tupled_args_count, args.len(), - receiver, + receiver_or_func, args, ); } @@ -1291,23 +1331,23 @@ impl<'tcx> ThirBuildCx<'tcx> { // Handle the receiver as the first arg, if present let mut args = Vec::with_capacity( - usize::from(receiver.is_some()) + usize::from(receiver_or_func.has_receiver()) + initial_non_tupled_args.len() + 1 + final_non_tupled_args.len(), ); - if let Some(receiver) = receiver { + if let Some(receiver) = receiver_or_func.receiver() { args.push(self.mirror_expr(receiver)); } args.extend(initial_non_tupled_args); args.push(tupled_args); args.extend(final_non_tupled_args); - let fn_span = if receiver.is_some() { fn_span } else { call_expr.span }; + let fn_span = if receiver_or_func.has_receiver() { fn_span } else { call_expr.span }; - let (fn_ty, fun_expr) = match (splatted_def, receiver) { + let (fn_ty, fun_expr) = match (splatted_def, receiver_or_func.fn_expression()) { // Create a FnDef shim for user-provided types - (SplattedDef { def_id: Some(def_id), arg_index, arg_count }, _) => { + (SplattedDef::FnDef { def_id, arg_index, arg_count }, _) => { // We're calling a function via a FnDef, and its possibly generic type // This is effectively `self.method_callee(call_expr, fn_span, None)`, // applied to `splatted_def` instead of `type_dependent_def`. @@ -1332,7 +1372,7 @@ impl<'tcx> ThirBuildCx<'tcx> { }; (callee_expr.ty, self.thir.exprs.push(callee_expr)) } - (SplattedDef { def_id: None, .. }, _) => { + (SplattedDef::NotAFnDef { not_yet_implemented: _, .. }, _) => { span_bug!(call_expr.span, "splatted FnPtr side-tables are not yet implemented"); } }; From f88563cfee254af537e9a8301dcb8269ed1a5b8b Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 28 Jul 2026 15:01:22 +1000 Subject: [PATCH 11/57] Make splatted FnPtr calls work (rather than ICE) Add tests for generic function pointers Change FnPtr tests to use assert_eq!() rather than println!() --- compiler/rustc_hir_typeck/src/callee.rs | 8 +- compiler/rustc_hir_typeck/src/expr.rs | 2 +- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 20 ++- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 2 + .../rustc_middle/src/ty/typeck_results.rs | 20 ++- compiler/rustc_mir_build/src/thir/cx/expr.rs | 30 +++- tests/ui/splat/splat-fn-ptr-cast.rs | 5 +- tests/ui/splat/splat-fn-ptr-generic.rs | 58 ++++++++ tests/ui/splat/splat-fn-ptr-ptr-tuple.rs | 130 ++++++++++++++---- tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr | 24 ---- tests/ui/splat/splat-fn-ptr-tuple-const.rs | 24 +--- .../ui/splat/splat-fn-ptr-tuple-const.stderr | 46 +------ tests/ui/splat/splat-fn-ptr-tuple-fail.rs | 18 +++ .../splat/splat-fn-ptr-tuple-fail.run.stderr | 3 + tests/ui/splat/splat-fn-ptr-tuple.rs | 73 +++++----- tests/ui/splat/splat-fn-ptr-tuple.stderr | 23 ---- 16 files changed, 290 insertions(+), 196 deletions(-) create mode 100644 tests/ui/splat/splat-fn-ptr-generic.rs delete mode 100644 tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr create mode 100644 tests/ui/splat/splat-fn-ptr-tuple-fail.rs create mode 100644 tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr delete mode 100644 tests/ui/splat/splat-fn-ptr-tuple.stderr diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 0caac74f2e4e1..fdbaa1a9e2e57 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -37,8 +37,10 @@ pub(crate) enum SplatLoweringInfo<'tcx> { /// The DefId of the FnDef being called, used to look up the function type. /// Also used during argument suggestion for non-splatted function calls. FnDef(DefId), - /// FIXME(splat): Stub for non-FnDef - NotAFnDef(std::marker::PhantomData<&'tcx ()>), + /// The type of the FnPtr being called. + FnPtr(Ty<'tcx>), + /// Type resolution errored. + Error(ErrorGuaranteed), } /// Checks that it is legal to call methods of the trait corresponding @@ -613,7 +615,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Splatted FnDefs use the DefId to look up the type, FnPtrs need it directly let fn_id = match def_id { Some(x) => SplatLoweringInfo::FnDef(x), - None => SplatLoweringInfo::NotAFnDef(std::marker::PhantomData), + None => SplatLoweringInfo::FnPtr(callee_ty), }; self.check_argument_types_maybe_method_like( diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 0e2720f4aa1d1..3909c736088b0 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1511,7 +1511,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { args, false, TupleArgumentsFlag::DontTupleArguments, - SplatLoweringInfo::NotAFnDef(std::marker::PhantomData), + SplatLoweringInfo::Error(guar), Some(GenericArgsRef::default()), ); diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index edfe1ec00f5e7..aaa4e4e643bc9 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -283,16 +283,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.write_args(hir_id, callee_generic_args); } } - // FIXME(splat): handle FnPtrs - SplatLoweringInfo::NotAFnDef(_) => { + // We're splatting a FnPtr based on its type + SplatLoweringInfo::FnPtr(fn_ty) => { + // FIXME(splat): do we need to look up both these HirIds? + // They can be different (and are different in some UI tests) self.write_splatted_resolution( hir_id, - Ok(SplattedDef::NotAFnDef { - not_yet_implemented: std::marker::PhantomData, + Ok(SplattedDef::FnPtr { + fn_ptr_type: fn_ty, arg_index: first_tupled_arg_index, arg_count: tupled_args_count, }), ); + // FIXME(splat): is this actually populated and used correctly? + if let Some(callee_generic_args) = callee_generic_args { + self.write_args(hir_id, callee_generic_args); + } + } + SplatLoweringInfo::Error(guar) => { + self.write_splatted_resolution(hir_id, Err(guar)); + if let Some(callee_generic_args) = callee_generic_args { + self.write_args(hir_id, callee_generic_args); + } } } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index cc614542015a2..005411915d17c 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -3513,6 +3513,8 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { format!("/* {} */", arg.name) } else { // FIXME(FnPtr, splat): What suggestions are needed for FnPtrs? + // SplatLoweringInfo::FnPtr(Ty) and SplatLoweringInfo::Error currently fall through to + // this placeholder "/* value */".to_string() } } diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index cf447eaa5838c..ff7cef3613437 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -614,9 +614,10 @@ pub enum SplattedDef<'tcx> { arg_count: u16, }, - /// FIXME(splat): handle FnPtrs - NotAFnDef { - not_yet_implemented: std::marker::PhantomData<&'tcx ()>, + /// A resolved FnPtr Call. + FnPtr { + /// The resolved type of the FnPtr. + fn_ptr_type: Ty<'tcx>, /// The index of the first argument in the callee's splatted tuple, and the index of the /// splatted tuple argument in the caller. @@ -631,21 +632,28 @@ impl<'tcx> SplattedDef<'tcx> { pub fn def_id(&self) -> Option { match self { SplattedDef::FnDef { def_id, .. } => Some(*def_id), - SplattedDef::NotAFnDef { .. } => None, + SplattedDef::FnPtr { .. } => None, + } + } + + pub fn fn_ptr_type(&self) -> Option> { + match self { + SplattedDef::FnDef { .. } => None, + SplattedDef::FnPtr { fn_ptr_type, .. } => Some(*fn_ptr_type), } } pub fn arg_index(&self) -> u16 { match self { SplattedDef::FnDef { arg_index, .. } => *arg_index, - SplattedDef::NotAFnDef { arg_index, .. } => *arg_index, + SplattedDef::FnPtr { arg_index, .. } => *arg_index, } } pub fn arg_count(&self) -> u16 { match self { SplattedDef::FnDef { arg_count, .. } => *arg_count, - SplattedDef::NotAFnDef { arg_count, .. } => *arg_count, + SplattedDef::FnPtr { arg_count, .. } => *arg_count, } } } diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 0db6cdc2f9ca5..fcf2432b4d8dc 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -1372,8 +1372,34 @@ impl<'tcx> ThirBuildCx<'tcx> { }; (callee_expr.ty, self.thir.exprs.push(callee_expr)) } - (SplattedDef::NotAFnDef { not_yet_implemented: _, .. }, _) => { - span_bug!(call_expr.span, "splatted FnPtr side-tables are not yet implemented"); + + // We're calling a function via a FnPtr and its type + // FIXME(splat): do we need to populate and apply user_provided_types() ? + (SplattedDef::FnPtr { fn_ptr_type, arg_index, arg_count }, Some(fn_expression)) => { + debug!( + "splatted_callee FnPtr: fn_ty={:?} arg_index={:?} arg_count={:?}", + fn_ptr_type, arg_index, arg_count, + ); + + if !fn_ptr_type.is_fn() { + span_bug!( + call_expr.span, + "splatted FnPtr side-tables were not populated correctly, non-fn type received: {:?}", + fn_ptr_type + ) + } + + // Pass through the FnPtr type and the mirrored function path + (fn_ptr_type, self.mirror_expr(fn_expression)) + } + // FnPtrs must have a function expression (and they never have method receivers) + (SplattedDef::FnPtr { .. }, None) => { + span_bug!( + call_expr.span, + "convert_splatted_callee: FnPtr without fn expression (or with receiver) is invalid: splatted_def={:?}, receiver_or_func={:?}", + splatted_def, + receiver_or_func, + ); } }; diff --git a/tests/ui/splat/splat-fn-ptr-cast.rs b/tests/ui/splat/splat-fn-ptr-cast.rs index 6e4a05ac2a776..9b1eac8a6fa85 100644 --- a/tests/ui/splat/splat-fn-ptr-cast.rs +++ b/tests/ui/splat/splat-fn-ptr-cast.rs @@ -8,9 +8,8 @@ fn main() { // Bug #158603 regression test variants #[rustfmt::skip] - let _x: fn(#[rustc_splat] (f32,)) = None.unwrap(); - // FIXME(splat): causes an ICE until #158603 is fixed - //x(1.0); + let x: fn(#[rustc_splat] (f32,)) = None.unwrap(); + x(1.0); let x: fn((i32,)) = None.unwrap(); x((1,)); diff --git a/tests/ui/splat/splat-fn-ptr-generic.rs b/tests/ui/splat/splat-fn-ptr-generic.rs new file mode 100644 index 0000000000000..a41fb0aa2a43a --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-generic.rs @@ -0,0 +1,58 @@ +//! Test using `#[rustc_splat]` on tuple arguments of pointers to generic functions. +//@ run-pass + +#![expect(incomplete_features)] +#![feature(splat, tuple_trait)] + +use std::fmt::Debug; +use std::marker::Tuple; + +fn generic(#[rustc_splat] a: T) -> String { + format!("{a:?}") +} + +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] +fn main() { + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> String + = generic as fn(#[rustc_splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> String + = generic::<(u32, i8)> as fn(#[rustc_splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr = generic as fn(#[rustc_splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr = generic::<(u32, i8)> as fn(#[rustc_splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> String = generic as _; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> String = generic::<(u32, i8)> as _; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + // Now without explicit `as`, this requires turbofish + let fn_ptr: fn(#[rustc_splat] (f64, i8)) -> String = generic::<(f64, i8)>; + assert_eq!(fn_ptr(3.5, -2), "(3.5, -2)"); + assert_eq!(fn_ptr(3.5f64, -2i8), "(3.5, -2)"); + + // FIXME(unused_variables): This is obviously used + #[expect(unused_variables)] + let fn_ptr = generic; + assert_eq!(fn_ptr(-1, 2, 3.5), "(-1, 2, 3.5)"); + assert_eq!(fn_ptr(-1i8, 2u32, 3.5f64), "(-1, 2, 3.5)"); + + #[expect(unused_variables)] + let fn_ptr = generic::<(i8, u32, f64)>; + assert_eq!(fn_ptr(-1, 2, 3.5), "(-1, 2, 3.5)"); + assert_eq!(fn_ptr(-1i8, 2u32, 3.5f64), "(-1, 2, 3.5)"); +} diff --git a/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs index fbe2d8c192f73..6473abce4b750 100644 --- a/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs +++ b/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs @@ -1,43 +1,113 @@ //! Test using `#[rustc_splat]` on tuple arguments of pointers to pointers to simple functions. -//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. +//! Bug #158603 regression test +//@ run-pass -//@ failure-status: 101 - -//@ normalize-stderr: ".*error:.*compiler/([^:]+):\d{1,}:\d{1,}:(.*)" -> "error: compiler/$1:LL:CC:$2" -//@ normalize-stderr: "thread.*panicked at .*compiler.*" -> "" -//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" -//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" -//@ normalize-stderr: " +\d{1,}: .*\n" -> "" -//@ normalize-stderr: " + at .*\n" -> "" -//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" -//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" -//@ normalize-stderr: ".*--> .*/splat-fn-ptr-tuple.rs:\d{1,}:\d{1,}.*\n" -> "" - -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(splat)] -fn tuple_args(#[rustc_splat] (_a, _b): (u32, i8)) {} +use std::ptr; + +fn tuple_args(#[rustc_splat] (a, b): (u32, i8)) -> (i8, u32) { + // Permute the returned values as a codegen test + (b, a) +} -fn splat_non_terminal_arg(#[rustc_splat] (_a, _b): (u32, i8), _c: f64) {} +fn splat_non_terminal_arg(#[rustc_splat] (a, b): (u32, i8), c: f64) -> (i8, f64, u32) { + // Permute the returned values as a codegen test + (b, c, a) +} +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] fn main() { - // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in - // MIR lowering - // FIXME(rustfmt): the attribute gets deleted by rustfmt - #[rustfmt::skip] - let fn_pp: *const fn(#[rustc_splat] (u32, i8)) - = tuple_args as *const fn(#[rustc_splat] (u32, i8)); + let fn_pp: &fn(#[rustc_splat] (u32, i8)) -> (i8, u32) + = &(tuple_args as fn(#[rustc_splat] (u32, i8)) -> (i8, u32)); + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + let fn_pp: &fn(#[rustc_splat] (u32, i8)) -> (i8, u32) = &(tuple_args as _); + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + let fn_pp = &(tuple_args as fn(#[rustc_splat] (u32, i8)) -> (i8, u32)); + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + // FIXME(unused_variables): This is obviously used + #[expect(unused_variables)] + let fn_pp = &tuple_args; + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + // Now with *const + let fn_pp: *const fn(#[rustc_splat] (u32, i8)) -> (i8, u32) + = ptr::from_ref(&(tuple_args as fn(#[rustc_splat] (u32, i8)) -> (i8, u32))); + unsafe { + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + } + + let fn_pp: *const fn(#[rustc_splat] (u32, i8)) -> (i8, u32) = ptr::from_ref(&(tuple_args as _)); + unsafe { + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + } + + let fn_pp = ptr::from_ref(&(tuple_args as fn(#[rustc_splat] (u32, i8)) -> (i8, u32))); + unsafe { + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + } + + #[expect(unused_variables)] + let fn_pp = ptr::from_ref(&tuple_args); + // FIXME(unsafe): dereferencing *const should require unsafe + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + // Now with *mut and non-terminal splat + let fn_pp: *mut fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32) + = ptr::from_mut( + &mut (splat_non_terminal_arg as fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32)) + ); + unsafe { + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + } + + let fn_pp: *mut fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32) + = ptr::from_mut(&mut (splat_non_terminal_arg as _)); + unsafe { + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + } + + let fn_pp = ptr::from_mut( + &mut (splat_non_terminal_arg as fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32)) + ); + unsafe { + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + } + + #[expect(unused_variables)] + let fn_pp = ptr::from_mut(&mut splat_non_terminal_arg); + // FIXME(unsafe): dereferencing *mut should require unsafe + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + + // Now with & as *const and non-terminal splat + let fn_pp: *const fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32) + = &(splat_non_terminal_arg as fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32)); unsafe { - (*fn_pp)(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented - // The ICE means that code after this line is not fully checked - (*fn_pp)(1u32, 2i8); + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); } - #[rustfmt::skip] - let fn_pp: *const fn(#[rustc_splat] (u32, i8), f64) = - splat_non_terminal_arg as *const fn(#[rustc_splat] (u32, i8), f64); + let fn_pp: *const fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32) + = &(splat_non_terminal_arg as _); unsafe { - (*fn_pp)(1, 2, 3.5); - (*fn_pp)(1u32, 2i8, 3.5f64); + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); } } diff --git a/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr deleted file mode 100644 index fd9fce68eb255..0000000000000 --- a/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr +++ /dev/null @@ -1,24 +0,0 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - --> $DIR/splat-fn-ptr-ptr-tuple.rs:31:9 - | -LL | (*fn_pp)(1, 2); - | ^^^^^^^^^^^^^^ - - - -Box -stack backtrace: - -note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md - -note: please make sure that you have updated to the latest nightly - -note: rustc {version} running on {platform} - -query stack during panic: -#0 [thir_body] building THIR for `main` -#1 [check_unsafety] unsafety-checking `main` -#2 [analysis] running analysis passes on crate `splat_fn_ptr_ptr_tuple` -end of query stack -error: aborting due to 1 previous error - diff --git a/tests/ui/splat/splat-fn-ptr-tuple-const.rs b/tests/ui/splat/splat-fn-ptr-tuple-const.rs index 035c4db9ad5be..c95c20fc89772 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple-const.rs +++ b/tests/ui/splat/splat-fn-ptr-tuple-const.rs @@ -1,17 +1,4 @@ //! Test using `#[rustc_splat]` on tuple arguments of generic function constants. -//! Currently ICEs (#158603), but if we fix it, we'll want to know and update this test to pass. - -//@ failure-status: 101 - -//@ normalize-stderr: ".*error:.*compiler/([^:]+):\d{1,}:\d{1,}:(.*)" -> "error: compiler/$1:LL:CC:$2" -//@ normalize-stderr: "thread.*panicked at .*compiler.*" -> "" -//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" -//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" -//@ normalize-stderr: " +\d{1,}: .*\n" -> "" -//@ normalize-stderr: " + at .*\n" -> "" -//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" -//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" -//@ normalize-stderr: ".*--> .*/splat-fn-ptr-tuple.rs:\d{1,}:\d{1,}.*\n" -> "" #![allow(incomplete_features)] #![feature(splat, tuple_trait)] @@ -20,15 +7,12 @@ use std::marker::Tuple; fn f(#[rustc_splat] args: Args) {} +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] fn main() { - // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in - // MIR lowering - // FIXME(rustfmt): the attribute gets deleted by rustfmt - #[rustfmt::skip] const F2: fn(#[rustc_splat] (u8, u32)) = f::<(u8, u32)>; - const R2: () = F2(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented + const R2: () = F2(1, 2); //~ ERROR function pointer calls are not allowed in constants - #[rustfmt::skip] const F1: fn(#[rustc_splat] ((u8, u32),)) = f::<((u8, u32),)>; - const R1: () = F1((1, 2)); //~ ERROR splatted FnPtr side-tables are not yet implemented + const R1: () = F1((1, 2)); //~ ERROR function pointer calls are not allowed in constants } diff --git a/tests/ui/splat/splat-fn-ptr-tuple-const.stderr b/tests/ui/splat/splat-fn-ptr-tuple-const.stderr index 1767782a9535e..f4b033445b3b2 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple-const.stderr +++ b/tests/ui/splat/splat-fn-ptr-tuple-const.stderr @@ -1,52 +1,14 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - --> $DIR/splat-fn-ptr-tuple-const.rs:29:20 +error: function pointer calls are not allowed in constants + --> $DIR/splat-fn-ptr-tuple-const.rs:14:20 | LL | const R2: () = F2(1, 2); | ^^^^^^^^ - - -Box -stack backtrace: - -note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md - -note: please make sure that you have updated to the latest nightly - -note: rustc {version} running on {platform} - -query stack during panic: -#0 [thir_body] building THIR for `main::R2` -#1 [check_match] match-checking `main::R2` -#2 [mir_built] building MIR for `main::R2` -#3 [trivial_const] checking if `main::R2` is a trivial const -#4 [eval_to_const_value_raw] simplifying constant for the type system `main::R2` -#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const` -end of query stack -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - --> $DIR/splat-fn-ptr-tuple-const.rs:33:20 +error: function pointer calls are not allowed in constants + --> $DIR/splat-fn-ptr-tuple-const.rs:17:20 | LL | const R1: () = F1((1, 2)); | ^^^^^^^^^^ - - -Box -stack backtrace: - -note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md - -note: please make sure that you have updated to the latest nightly - -note: rustc {version} running on {platform} - -query stack during panic: -#0 [thir_body] building THIR for `main::R1` -#1 [check_match] match-checking `main::R1` -#2 [mir_built] building MIR for `main::R1` -#3 [trivial_const] checking if `main::R1` is a trivial const -#4 [eval_to_const_value_raw] simplifying constant for the type system `main::R1` -#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const` -end of query stack error: aborting due to 2 previous errors diff --git a/tests/ui/splat/splat-fn-ptr-tuple-fail.rs b/tests/ui/splat/splat-fn-ptr-tuple-fail.rs new file mode 100644 index 0000000000000..a76f9f30cae32 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple-fail.rs @@ -0,0 +1,18 @@ +//! Test using `#[rustc_splat]` on tuple arguments of pointers to invalid simple functions. +//! Bug #158603 regression test +//@ run-fail +//@ check-run-results +//@ exec-env: RUST_BACKTRACE=0 + +//@ normalize-stderr: "thread '.*'" -> "thread 'NAME'" +//@ normalize-stderr: "note: run with.*\n" -> "" + +#![expect(incomplete_features)] +#![feature(splat)] + +fn main() { + // FIXME(rustfmt): the attribute gets deleted by rustfmt + #[rustfmt::skip] + let x: fn(#[rustc_splat] (i32,)) = None.unwrap(); + x(1); +} diff --git a/tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr b/tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr new file mode 100644 index 0000000000000..7536f99c69bd6 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr @@ -0,0 +1,3 @@ + +thread 'NAME' ($TID) panicked at $DIR/splat-fn-ptr-tuple-fail.rs:16:45: +called `Option::unwrap()` on a `None` value diff --git a/tests/ui/splat/splat-fn-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-tuple.rs index 7fb06ad1c6bc1..23690865e9aaf 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple.rs +++ b/tests/ui/splat/splat-fn-ptr-tuple.rs @@ -1,46 +1,43 @@ //! Test using `#[rustc_splat]` on tuple arguments of pointers to simple functions. -//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. +//! Bug #158603 regression test +//@ run-pass -//@ failure-status: 101 - -//@ normalize-stderr: ".*error:.*compiler/([^:]+):\d{1,}:\d{1,}:(.*)" -> "error: compiler/$1:LL:CC:$2" -//@ normalize-stderr: "thread.*panicked at .*compiler.*" -> "" -//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" -//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" -//@ normalize-stderr: " +\d{1,}: .*\n" -> "" -//@ normalize-stderr: " + at .*\n" -> "" -//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" -//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" -//@ normalize-stderr: ".*--> .*/splat-fn-ptr-tuple.rs:\d{1,}:\d{1,}.*\n" -> "" - -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(splat)] -fn tuple_args(#[rustc_splat] (_a, _b): (u32, i8)) {} +fn tuple_args(#[rustc_splat] (a, b): (u32, i8)) -> (u32, i8) { + (a, b) +} -fn splat_non_terminal_arg(#[rustc_splat] (_a, _b): (u32, i8), _c: f64) {} +fn splat_non_terminal_arg(#[rustc_splat] (a, b): (u32, i8), c: f64) -> (f64, i8, u32) { + // Permute the returned values as a codegen test + (c, b, a) +} +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] fn main() { - // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in - // MIR lowering - // FIXME(rustfmt): the attribute gets deleted by rustfmt - #[rustfmt::skip] - let fn_ptr: fn(#[rustc_splat] (u32, i8)) = tuple_args; - fn_ptr(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented - // The ICE means that code after this line is not fully checked - fn_ptr(1u32, 2i8); - - // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? - // Add a tupled test for each call if they are. - //fn_ptr((1, 2)); // ERROR this splatted function takes 2 arguments, but 1 was provided - - #[rustfmt::skip] - let fn_ptr: fn(#[rustc_splat] (u32, i8), f64) = splat_non_terminal_arg; - fn_ptr(1, 2, 3.5); - fn_ptr(1u32, 2i8, 3.5f64); - - // Bug #158603 regression test - #[rustfmt::skip] - let x: fn(#[rustc_splat] (i32,)) = None.unwrap(); - x(1); + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> (u32, i8) + = tuple_args as fn(#[rustc_splat] (u32, i8)) -> (u32, i8); + assert_eq!(fn_ptr(1, 2), (1, 2)); + assert_eq!(fn_ptr(1u32, 2i8), (1u32, 2i8)); + + let fn_ptr = tuple_args as fn(#[rustc_splat] (u32, i8)) -> (u32, i8); + assert_eq!(fn_ptr(1, 2), (1, 2)); + assert_eq!(fn_ptr(1u32, 2i8), (1u32, 2i8)); + + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> (u32, i8) = tuple_args as _; + assert_eq!(fn_ptr(1, 2), (1, 2)); + assert_eq!(fn_ptr(1u32, 2i8), (1u32, 2i8)); + + // Now without explicit `as` + let fn_ptr: fn(#[rustc_splat] (u32, i8), f64) -> (f64, i8, u32) = splat_non_terminal_arg; + assert_eq!(fn_ptr(1, 2, 3.5), (3.5, 2, 1)); + assert_eq!(fn_ptr(1u32, 2i8, 3.5f64), (3.5f64, 2i8, 1u32)); + + // FIXME(unused_variables): This is obviously used + #[expect(unused_variables)] + let fn_ptr = splat_non_terminal_arg; + assert_eq!(fn_ptr(1, 2, 3.5), (3.5, 2, 1)); + assert_eq!(fn_ptr(1u32, 2i8, 3.5f64), (3.5f64, 2i8, 1u32)); } diff --git a/tests/ui/splat/splat-fn-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-tuple.stderr deleted file mode 100644 index 4cc861cafe968..0000000000000 --- a/tests/ui/splat/splat-fn-ptr-tuple.stderr +++ /dev/null @@ -1,23 +0,0 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - | -LL | fn_ptr(1, 2); - | ^^^^^^^^^^^^ - - - -Box -stack backtrace: - -note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md - -note: please make sure that you have updated to the latest nightly - -note: rustc {version} running on {platform} - -query stack during panic: -#0 [thir_body] building THIR for `main` -#1 [check_unsafety] unsafety-checking `main` -#2 [analysis] running analysis passes on crate `splat_fn_ptr_tuple` -end of query stack -error: aborting due to 1 previous error - From 9ff75a767e1c5b0e0d666483f743e7a9a26e9b61 Mon Sep 17 00:00:00 2001 From: lcnr Date: Mon, 3 Aug 2026 20:04:21 +0200 Subject: [PATCH 12/57] make `DefiningTy` independent of borrowck --- .../rustc_borrowck/src/universal_regions.rs | 449 +++++++++--------- 1 file changed, 219 insertions(+), 230 deletions(-) diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs index 2d4d98d812c65..694f29b942e4f 100644 --- a/compiler/rustc_borrowck/src/universal_regions.rs +++ b/compiler/rustc_borrowck/src/universal_regions.rs @@ -134,6 +134,204 @@ pub(crate) enum DefiningTy<'tcx> { } impl<'tcx> DefiningTy<'tcx> { + #[instrument(level = "debug", skip(tcx), ret)] + fn new(tcx: TyCtxt<'tcx>, body_def_id: LocalDefId) -> DefiningTy<'tcx> { + match tcx.hir_body_owner_kind(body_def_id) { + BodyOwnerKind::Closure | BodyOwnerKind::Fn => { + let defining_ty = tcx.type_of(body_def_id).instantiate_identity().skip_norm_wip(); + match *defining_ty.kind() { + ty::Closure(def_id, args) => DefiningTy::Closure(def_id, args), + ty::Coroutine(def_id, args) => DefiningTy::Coroutine(def_id, args), + ty::CoroutineClosure(def_id, args) => { + DefiningTy::CoroutineClosure(def_id, args) + } + ty::FnDef(def_id, args) => { + DefiningTy::FnDef(def_id, args.no_bound_vars().unwrap()) + } + _ => span_bug!( + tcx.def_span(body_def_id), + "expected defining type for `{body_def_id:?}`: `{defining_ty:?}`", + ), + } + } + + BodyOwnerKind::Const { .. } | BodyOwnerKind::Static(..) => { + match tcx.def_kind(body_def_id) { + DefKind::AnonConst + if tcx.anon_const_kind(body_def_id) + == ty::AnonConstKind::NonTypeSystemInline => + { + // This is required for `AscribeUserType` canonical query, which will call + // `type_of(inline_const_def_id)`. That `type_of` would inject erased lifetimes + // into borrowck, which is ICE #78174. + // + // As a workaround, inline consts have an additional generic param (`ty` + // below), so that `type_of(inline_const_def_id).substs(substs)` uses the + // proper type with NLL infer vars. + // + // Fetch the actual type from MIR, as `type_of` returns something useless + // like ``. + let body = tcx.mir_promoted(body_def_id).0.borrow(); + let ty = body.local_decls[RETURN_PLACE].ty; + let typeck_root_def_id = tcx.typeck_root_def_id(body_def_id.to_def_id()); + let parent_args = GenericArgs::identity_for_item(tcx, typeck_root_def_id); + let args = + InlineConstArgs::new(tcx, InlineConstArgsParts { parent_args, ty }) + .args; + DefiningTy::InlineConst(body_def_id.to_def_id(), args) + } + _ => { + let args = GenericArgs::identity_for_item(tcx, body_def_id.to_def_id()); + DefiningTy::Const(body_def_id.to_def_id(), args) + } + } + } + + BodyOwnerKind::GlobalAsm => DefiningTy::GlobalAsm(body_def_id.to_def_id()), + } + } + + #[instrument(level = "debug", skip(tcx, c_variadic_region), ret)] + fn inputs_and_output( + self, + tcx: TyCtxt<'tcx>, + c_variadic_region: impl FnOnce() -> ty::Region<'tcx>, + ) -> ty::Binder<'tcx, &'tcx ty::List>> { + match self { + DefiningTy::Closure(def_id, args) => { + let closure_sig = args.as_closure().sig(); + let inputs_and_output = closure_sig.inputs_and_output(); + let bound_vars = tcx.mk_bound_variable_kinds_from_iter( + inputs_and_output.bound_vars().iter().chain(iter::once( + ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv), + )), + ); + let br = ty::BoundRegion { + var: ty::BoundVar::from_usize(bound_vars.len() - 1), + kind: ty::BoundRegionKind::ClosureEnv, + }; + let env_region = ty::Region::new_bound(tcx, ty::INNERMOST, br); + let closure_ty = tcx.closure_env_ty( + Ty::new_closure(tcx, def_id, args), + args.as_closure().kind(), + env_region, + ); + + // The "inputs" of the closure in the + // signature appear as a tuple. The MIR side + // flattens this tuple. + let (&output, tuplized_inputs) = + inputs_and_output.skip_binder().split_last().unwrap(); + assert_eq!(tuplized_inputs.len(), 1, "multiple closure inputs"); + let &ty::Tuple(inputs) = tuplized_inputs[0].kind() else { + bug!("closure inputs not a tuple: {:?}", tuplized_inputs[0]); + }; + + ty::Binder::bind_with_vars( + tcx.mk_type_list_from_iter( + iter::once(closure_ty).chain(inputs).chain(iter::once(output)), + ), + bound_vars, + ) + } + + DefiningTy::Coroutine(def_id, args) => { + let resume_ty = args.as_coroutine().resume_ty(); + let output = args.as_coroutine().return_ty(); + let coroutine_ty = Ty::new_coroutine(tcx, def_id, args); + let inputs_and_output = tcx.mk_type_list(&[coroutine_ty, resume_ty, output]); + ty::Binder::dummy(inputs_and_output) + } + + // Construct the signature of the CoroutineClosure for the purposes of borrowck. + // This is pretty straightforward -- we: + // 1. first grab the `coroutine_closure_sig`, + // 2. compute the self type (`&`/`&mut`/no borrow), + // 3. flatten the tupled_input_tys, + // 4. construct the correct generator type to return with + // `CoroutineClosureSignature::to_coroutine_given_kind_and_upvars`. + // Then we wrap it all up into a list of inputs and output. + DefiningTy::CoroutineClosure(def_id, args) => { + let closure_sig = args.as_coroutine_closure().coroutine_closure_sig(); + let bound_vars = + tcx.mk_bound_variable_kinds_from_iter(closure_sig.bound_vars().iter().chain( + iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv)), + )); + let br = ty::BoundRegion { + var: ty::BoundVar::from_usize(bound_vars.len() - 1), + kind: ty::BoundRegionKind::ClosureEnv, + }; + let env_region = ty::Region::new_bound(tcx, ty::INNERMOST, br); + let closure_kind = args.as_coroutine_closure().kind(); + + let closure_ty = tcx.closure_env_ty( + Ty::new_coroutine_closure(tcx, def_id, args), + closure_kind, + env_region, + ); + + let inputs = closure_sig.skip_binder().tupled_inputs_ty.tuple_fields(); + let output = closure_sig.skip_binder().to_coroutine_given_kind_and_upvars( + tcx, + args.as_coroutine_closure().parent_args(), + tcx.coroutine_for_closure(def_id), + closure_kind, + env_region, + args.as_coroutine_closure().tupled_upvars_ty(), + args.as_coroutine_closure().coroutine_captures_by_ref_ty(), + ); + + ty::Binder::bind_with_vars( + tcx.mk_type_list_from_iter( + iter::once(closure_ty).chain(inputs).chain(iter::once(output)), + ), + bound_vars, + ) + } + + DefiningTy::FnDef(def_id, _) => { + let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); + let inputs_and_output = sig.inputs_and_output(); + + // C-variadic fns also have a `VaList` input that's not listed in the signature + // (as it's created inside the body itself, not passed in from outside). + if tcx.fn_sig(def_id).skip_binder().c_variadic() { + let va_list_did = tcx.require_lang_item(LangItem::VaList, tcx.def_span(def_id)); + + let region = c_variadic_region(); + let va_list_ty = + tcx.type_of(va_list_did).instantiate(tcx, &[region.into()]).skip_norm_wip(); + + // The signature needs to follow the order [input_tys, va_list_ty, output_ty] + return inputs_and_output.map_bound(|tys| { + let (output_ty, input_tys) = tys.split_last().unwrap(); + tcx.mk_type_list_from_iter( + input_tys.iter().copied().chain([va_list_ty, *output_ty]), + ) + }); + } + + inputs_and_output + } + + DefiningTy::Const(def_id, _) => { + // For a constant body, there are no inputs, and one + // "output" (the type of the constant). + let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip(); + ty::Binder::dummy(tcx.mk_type_list(&[ty])) + } + + DefiningTy::InlineConst(_def_id, args) => { + let ty = args.as_inline_const().ty(); + ty::Binder::dummy(tcx.mk_type_list(&[ty])) + } + + DefiningTy::GlobalAsm(def_id) => ty::Binder::dummy( + tcx.mk_type_list(&[tcx.type_of(def_id).instantiate_identity().skip_norm_wip()]), + ), + } + } + /// Returns a list of all the upvar types for this MIR. If this is /// not a closure or coroutine, there are no upvars, and hence it /// will be an empty list. The order of types in this list will @@ -581,82 +779,23 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { } } - /// Returns the "defining type" of the current MIR; - /// see `DefiningTy` for details. + /// Returns the "defining type" of the current MIR; see `DefiningTy` for details. fn defining_ty(&self) -> DefiningTy<'tcx> { - let tcx = self.infcx.tcx; - - match tcx.hir_body_owner_kind(self.mir_def) { - BodyOwnerKind::Closure | BodyOwnerKind::Fn => { - let defining_ty = tcx.type_of(self.mir_def).instantiate_identity().skip_norm_wip(); - - debug!("defining_ty (pre-replacement): {:?}", defining_ty); - - let defining_ty = self.infcx.replace_free_regions_with_nll_infer_vars( - NllRegionVariableOrigin::FreeRegion, - defining_ty, - ); - - match *defining_ty.kind() { - ty::Closure(def_id, args) => DefiningTy::Closure(def_id, args), - ty::Coroutine(def_id, args) => DefiningTy::Coroutine(def_id, args), - ty::CoroutineClosure(def_id, args) => { - DefiningTy::CoroutineClosure(def_id, args) - } - ty::FnDef(def_id, args) => { - DefiningTy::FnDef(def_id, args.no_bound_vars().unwrap()) - } - _ => span_bug!( - tcx.def_span(self.mir_def), - "expected defining type for `{:?}`: `{:?}`", - self.mir_def, - defining_ty - ), - } - } - - BodyOwnerKind::Const { .. } | BodyOwnerKind::Static(..) => { - match tcx.def_kind(self.mir_def) { - DefKind::AnonConst - if tcx.anon_const_kind(self.mir_def) - == ty::AnonConstKind::NonTypeSystemInline => - { - // This is required for `AscribeUserType` canonical query, which will call - // `type_of(inline_const_def_id)`. That `type_of` would inject erased lifetimes - // into borrowck, which is ICE #78174. - // - // As a workaround, inline consts have an additional generic param (`ty` - // below), so that `type_of(inline_const_def_id).substs(substs)` uses the - // proper type with NLL infer vars. - // - // Fetch the actual type from MIR, as `type_of` returns something useless - // like ``. - let body = tcx.mir_promoted(self.mir_def).0.borrow(); - let ty = body.local_decls[RETURN_PLACE].ty; - let typeck_root_def_id = tcx.typeck_root_def_id(self.mir_def.to_def_id()); - let parent_args = GenericArgs::identity_for_item(tcx, typeck_root_def_id); - let args = - InlineConstArgs::new(tcx, InlineConstArgsParts { parent_args, ty }) - .args; - let args = self.infcx.replace_free_regions_with_nll_infer_vars( - NllRegionVariableOrigin::FreeRegion, - args, - ); - DefiningTy::InlineConst(self.mir_def.to_def_id(), args) - } - _ => { - let identity_args = - GenericArgs::identity_for_item(tcx, self.mir_def.to_def_id()); - let args = self.infcx.replace_free_regions_with_nll_infer_vars( - NllRegionVariableOrigin::FreeRegion, - identity_args, - ); - DefiningTy::Const(self.mir_def.to_def_id(), args) - } - } + let defining_ty = DefiningTy::new(self.infcx.tcx, self.mir_def); + let f = |args| { + let fr = NllRegionVariableOrigin::FreeRegion; + self.infcx.replace_free_regions_with_nll_infer_vars(fr, args) + }; + match defining_ty { + DefiningTy::Closure(def_id, args) => DefiningTy::Closure(def_id, f(args)), + DefiningTy::Coroutine(def_id, args) => DefiningTy::Coroutine(def_id, f(args)), + DefiningTy::CoroutineClosure(def_id, args) => { + DefiningTy::CoroutineClosure(def_id, f(args)) } - - BodyOwnerKind::GlobalAsm => DefiningTy::GlobalAsm(self.mir_def.to_def_id()), + DefiningTy::FnDef(def_id, args) => DefiningTy::FnDef(def_id, f(args)), + DefiningTy::Const(def_id, args) => DefiningTy::Const(def_id, f(args)), + DefiningTy::InlineConst(def_id, args) => DefiningTy::InlineConst(def_id, f(args)), + DefiningTy::GlobalAsm(def_id) => DefiningTy::GlobalAsm(def_id), } } @@ -694,163 +833,13 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { defining_ty: DefiningTy<'tcx>, ) -> ty::Binder<'tcx, &'tcx ty::List>> { let tcx = self.infcx.tcx; + let inputs_and_output = defining_ty.inputs_and_output(tcx, || { + self.infcx.next_nll_region_var(NllRegionVariableOrigin::FreeRegion, || { + RegionCtxt::Free(sym::c_dash_variadic) + }) + }); - let inputs_and_output = match defining_ty { - DefiningTy::Closure(def_id, args) => { - assert_eq!(self.mir_def.to_def_id(), def_id); - let closure_sig = args.as_closure().sig(); - let inputs_and_output = closure_sig.inputs_and_output(); - let bound_vars = tcx.mk_bound_variable_kinds_from_iter( - inputs_and_output.bound_vars().iter().chain(iter::once( - ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv), - )), - ); - let br = ty::BoundRegion { - var: ty::BoundVar::from_usize(bound_vars.len() - 1), - kind: ty::BoundRegionKind::ClosureEnv, - }; - let env_region = ty::Region::new_bound(tcx, ty::INNERMOST, br); - let closure_ty = tcx.closure_env_ty( - Ty::new_closure(tcx, def_id, args), - args.as_closure().kind(), - env_region, - ); - - // The "inputs" of the closure in the - // signature appear as a tuple. The MIR side - // flattens this tuple. - let (&output, tuplized_inputs) = - inputs_and_output.skip_binder().split_last().unwrap(); - assert_eq!(tuplized_inputs.len(), 1, "multiple closure inputs"); - let &ty::Tuple(inputs) = tuplized_inputs[0].kind() else { - bug!("closure inputs not a tuple: {:?}", tuplized_inputs[0]); - }; - - ty::Binder::bind_with_vars( - tcx.mk_type_list_from_iter( - iter::once(closure_ty).chain(inputs).chain(iter::once(output)), - ), - bound_vars, - ) - } - - DefiningTy::Coroutine(def_id, args) => { - assert_eq!(self.mir_def.to_def_id(), def_id); - let resume_ty = args.as_coroutine().resume_ty(); - let output = args.as_coroutine().return_ty(); - let coroutine_ty = Ty::new_coroutine(tcx, def_id, args); - let inputs_and_output = - self.infcx.tcx.mk_type_list(&[coroutine_ty, resume_ty, output]); - ty::Binder::dummy(inputs_and_output) - } - - // Construct the signature of the CoroutineClosure for the purposes of borrowck. - // This is pretty straightforward -- we: - // 1. first grab the `coroutine_closure_sig`, - // 2. compute the self type (`&`/`&mut`/no borrow), - // 3. flatten the tupled_input_tys, - // 4. construct the correct generator type to return with - // `CoroutineClosureSignature::to_coroutine_given_kind_and_upvars`. - // Then we wrap it all up into a list of inputs and output. - DefiningTy::CoroutineClosure(def_id, args) => { - assert_eq!(self.mir_def.to_def_id(), def_id); - let closure_sig = args.as_coroutine_closure().coroutine_closure_sig(); - let bound_vars = - tcx.mk_bound_variable_kinds_from_iter(closure_sig.bound_vars().iter().chain( - iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv)), - )); - let br = ty::BoundRegion { - var: ty::BoundVar::from_usize(bound_vars.len() - 1), - kind: ty::BoundRegionKind::ClosureEnv, - }; - let env_region = ty::Region::new_bound(tcx, ty::INNERMOST, br); - let closure_kind = args.as_coroutine_closure().kind(); - - let closure_ty = tcx.closure_env_ty( - Ty::new_coroutine_closure(tcx, def_id, args), - closure_kind, - env_region, - ); - - let inputs = closure_sig.skip_binder().tupled_inputs_ty.tuple_fields(); - let output = closure_sig.skip_binder().to_coroutine_given_kind_and_upvars( - tcx, - args.as_coroutine_closure().parent_args(), - tcx.coroutine_for_closure(def_id), - closure_kind, - env_region, - args.as_coroutine_closure().tupled_upvars_ty(), - args.as_coroutine_closure().coroutine_captures_by_ref_ty(), - ); - - ty::Binder::bind_with_vars( - tcx.mk_type_list_from_iter( - iter::once(closure_ty).chain(inputs).chain(iter::once(output)), - ), - bound_vars, - ) - } - - DefiningTy::FnDef(def_id, _) => { - let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); - let sig = indices.fold_to_region_vids(tcx, sig); - let inputs_and_output = sig.inputs_and_output(); - - // C-variadic fns also have a `VaList` input that's not listed in the signature - // (as it's created inside the body itself, not passed in from outside). - if self.infcx.tcx.fn_sig(def_id).skip_binder().c_variadic() { - let va_list_did = self - .infcx - .tcx - .require_lang_item(LangItem::VaList, self.infcx.tcx.def_span(self.mir_def)); - - let reg_vid = self - .infcx - .next_nll_region_var(NllRegionVariableOrigin::FreeRegion, || { - RegionCtxt::Free(sym::c_dash_variadic) - }) - .as_var(); - - let region = ty::Region::new_var(self.infcx.tcx, reg_vid); - let va_list_ty = self - .infcx - .tcx - .type_of(va_list_did) - .instantiate(self.infcx.tcx, &[region.into()]) - .skip_norm_wip(); - - // The signature needs to follow the order [input_tys, va_list_ty, output_ty] - return inputs_and_output.map_bound(|tys| { - let (output_ty, input_tys) = tys.split_last().unwrap(); - tcx.mk_type_list_from_iter( - input_tys.iter().copied().chain([va_list_ty, *output_ty]), - ) - }); - } - - inputs_and_output - } - - DefiningTy::Const(def_id, _) => { - // For a constant body, there are no inputs, and one - // "output" (the type of the constant). - assert_eq!(self.mir_def.to_def_id(), def_id); - let ty = tcx.type_of(self.mir_def).instantiate_identity().skip_norm_wip(); - - let ty = indices.fold_to_region_vids(tcx, ty); - ty::Binder::dummy(tcx.mk_type_list(&[ty])) - } - - DefiningTy::InlineConst(def_id, args) => { - assert_eq!(self.mir_def.to_def_id(), def_id); - let ty = args.as_inline_const().ty(); - ty::Binder::dummy(tcx.mk_type_list(&[ty])) - } - - DefiningTy::GlobalAsm(def_id) => ty::Binder::dummy( - tcx.mk_type_list(&[tcx.type_of(def_id).instantiate_identity().skip_norm_wip()]), - ), - }; + let inputs_and_output = indices.fold_to_region_vids(tcx, inputs_and_output); // FIXME(#129952): We probably want a more principled approach here. if let Err(e) = inputs_and_output.error_reported() { From 8427cb5e21a84525d50f987f482e571e2fa300cd Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:56:19 +0330 Subject: [PATCH 13/57] Add regression test for associated type outlives bound at call site --- ...oc-type-outlives-via-where-clause-63253.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/ui/associated-types/assoc-type-outlives-via-where-clause-63253.rs diff --git a/tests/ui/associated-types/assoc-type-outlives-via-where-clause-63253.rs b/tests/ui/associated-types/assoc-type-outlives-via-where-clause-63253.rs new file mode 100644 index 0000000000000..ad675d4bc848e --- /dev/null +++ b/tests/ui/associated-types/assoc-type-outlives-via-where-clause-63253.rs @@ -0,0 +1,33 @@ +//! Regression test for . +//! +//! A `where Self::Ty: 'a` bound on the callee was not being used to prove the +//! associated type outlives `'a` at the call site, so both of these calls used +//! to fail with E0309 ("the associated type `>::Ty` may not live +//! long enough"). + +//@ check-pass + +#![allow(unused)] + +// The associated function is reached through a method-call path. +trait Trait<'a> { + type Ty; + fn method(ty_ref: &'a Self::Ty) where Self::Ty: 'a {} +} + +fn caller<'a, T: Trait<'a>>(arg: &'a T::Ty) where T::Ty: 'a { + T::method(arg) +} + +// The same bound, reached through a free function instead. +trait Trait2<'a> { + type Ty; +} + +fn free_fn<'a, T: Trait2<'a>>(_arg: &'a T::Ty) where T::Ty: 'a {} + +fn free_fn_caller<'a, T: Trait2<'a>>(arg: &'a T::Ty) where T::Ty: 'a { + free_fn::(arg) +} + +fn main() {} From cd19505b4ae92957e6d631c92a0bb325691aee73 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:44:54 +0200 Subject: [PATCH 14/57] rustc_resolve: move diagnostic attribute linting to attr parsing It had to be in rustc_resolve because there was no general attribute parsing infra back then, but now there is, so it should be there --- .../src/attributes/diagnostic/mod.rs | 68 ++++++++++++++++++- .../src/attributes/diagnostic/on_const.rs | 8 +-- .../src/attributes/diagnostic/on_move.rs | 34 ++++------ .../attributes/diagnostic/on_type_error.rs | 34 +++------- .../src/attributes/diagnostic/on_unknown.rs | 34 ++++------ .../diagnostic/on_unmatched_args.rs | 8 +-- .../src/attributes/diagnostic/opaque.rs | 7 +- .../rustc_attr_parsing/src/diagnostics.rs | 29 ++++++++ compiler/rustc_attr_parsing/src/interface.rs | 3 + compiler/rustc_attr_parsing/src/lib.rs | 2 + compiler/rustc_resolve/src/diagnostics/mod.rs | 24 ------- compiler/rustc_resolve/src/macros.rs | 58 +--------------- .../feature-gate-diagnostic-on-move.stderr | 5 +- ...nostic-on-type-error-malformed-args.stderr | 5 +- .../feature-gate-diagnostic-on-type-error.rs | 1 + ...ature-gate-diagnostic-on-type-error.stderr | 9 +-- .../feature-gate-diagnostic-on-unknown.stderr | 5 +- .../feature-gate-diagnostic-opaque.stderr | 10 +-- 18 files changed, 165 insertions(+), 179 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index 1264ad6597561..a813e0ba02a89 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -1,16 +1,20 @@ use std::ops::Range; +use rustc_ast::PathSegment; +use rustc_errors::{Diagnostic, MultiSpan}; use rustc_hir::attrs::diagnostic::{ Directive, Filter, FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, Name, NameValue, Piece, Predicate, }; +use rustc_lint_defs::LintId; use rustc_parse_format::{ Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position, }; use rustc_session::lint::builtin::{ MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FILTERS, - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, UNKNOWN_DIAGNOSTIC_ATTRIBUTES, }; +use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::{Ident, InnerSpan, Span, Symbol, kw, sym}; use thin_vec::{ThinVec, thin_vec}; @@ -20,6 +24,7 @@ use crate::diagnostics::{ MissingOptionsForDiagnosticAttribute, NonMetaItemDiagnosticAttribute, WrappedParserError, }; use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser}; +use crate::{EmitAttribute, diagnostics}; pub(crate) mod do_not_recommend; pub(crate) mod on_const; @@ -30,6 +35,67 @@ pub(crate) mod on_unknown; pub(crate) mod on_unmatched_args; pub(crate) mod opaque; +impl<'sess> crate::AttributeParser<'sess> { + pub(crate) fn unknown_diagnostic_attr( + &self, + segment: &PathSegment, + mut emit_lint: impl FnMut(LintId, MultiSpan, EmitAttribute), + ) { + const DIAGNOSTIC_ATTRIBUTES: [( + Symbol, /* name */ + Option, /* feature gate */ + ); 8] = [ + (sym::on_unimplemented, None), + (sym::do_not_recommend, None), + (sym::on_move, Some(sym::diagnostic_on_move)), + (sym::on_const, Some(sym::diagnostic_on_const)), + (sym::on_unknown, Some(sym::diagnostic_on_unknown)), + (sym::on_unmatched_args, Some(sym::diagnostic_on_unmatched_args)), + (sym::on_type_error, Some(sym::diagnostic_on_type_error)), + (sym::opaque, Some(sym::diagnostic_opaque)), + ]; + // No need to emit a lint if features aren't available. + let Some(features) = self.features else { return }; + let span = segment.span(); + let candidates = DIAGNOSTIC_ATTRIBUTES + .iter() + .filter_map(|(attr, feature)| { + feature.is_none_or(|f| features.enabled(f)).then_some(*attr) + }) + .collect::>(); + + let typo = find_best_match_for_name(&candidates, segment.ident.name, None) + .map(|typo_name| diagnostics::UnknownDiagnosticAttributeTypo { span, typo_name }); + emit_lint( + LintId::of(UNKNOWN_DIAGNOSTIC_ATTRIBUTES), + span.into(), + EmitAttribute(Box::new(move |dcx, level, _| { + diagnostics::UnknownDiagnosticAttribute { typo }.into_diag(dcx, level) + })), + ) + } +} + +#[rustc_macro_transparency = "transparent"] +macro gate_diagnostic_attr($feature:ident) {{ + if let Some(features) = cx.features_option() + && !features.$feature() + { + args.ignore_args(); + let nightly_build = cx.sess.is_nightly_build(); + let span = cx.attr_span; + cx.emit_lint( + rustc_lint_defs::builtin::UNKNOWN_DIAGNOSTIC_ATTRIBUTES, + $crate::diagnostics::UnstableDiagnosticAttribute { + feature: sym::$feature, + nightly_build, + }, + span, + ); + return; + } +}} + #[derive(Copy, Clone)] pub(crate) enum Mode { /// `#[rustc_on_unimplemented]` diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs index 20c0ac7cc8554..f92a7694ec357 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs @@ -13,13 +13,9 @@ impl AttributeParser for OnConstParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_const], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), - AttributeStability::Stable, // Unstable, stability checked manually in the parser + AttributeStability::Stable, // Unstable, stability checked manually below |this, cx, args| { - if !cx.features().diagnostic_on_const() { - // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs - args.ignore_args(); - return; - } + gate_diagnostic_attr!(diagnostic_on_const); let path_span = cx.attr_path.span; this.path_span = Some(path_span); diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs index 1c8b3418fa746..dcba5d5b301a6 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs @@ -4,8 +4,6 @@ use rustc_span::sym; use crate::attributes::diagnostic::*; use crate::attributes::prelude::*; -use crate::context::AcceptContext; -use crate::parser::ArgParser; use crate::target_checking::AllowedTargets; use crate::template; @@ -15,31 +13,23 @@ pub(crate) struct OnMoveParser { directive: Option<(Span, Directive)>, } -impl OnMoveParser { - fn parse<'sess>(&mut self, cx: &mut AcceptContext<'_, 'sess>, args: &ArgParser, mode: Mode) { - if !cx.features().diagnostic_on_move() { - // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs - args.ignore_args(); - return; - } - - let span = cx.attr_span; - self.span = Some(span); - - let Some(items) = parse_list(cx, args, mode) else { return }; - - if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { - merge_directives(cx, &mut self.directive, (span, directive)); - } - } -} impl AttributeParser for OnMoveParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_move], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), - AttributeStability::Stable, // Unstable, stability checked manually in the parser + AttributeStability::Stable, // Unstable, stability checked manually below |this, cx, args| { - this.parse(cx, args, Mode::DiagnosticOnMove); + gate_diagnostic_attr!(diagnostic_on_move); + + let span = cx.attr_span; + this.span = Some(span); + let mode = Mode::DiagnosticOnMove; + + let Some(items) = parse_list(cx, args, mode) else { return }; + + if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { + merge_directives(cx, &mut this.directive, (span, directive)); + } }, )]; diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_type_error.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_type_error.rs index 38c1f9ab6c945..1bdde3af7f0eb 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_type_error.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_type_error.rs @@ -4,8 +4,6 @@ use rustc_span::sym; use crate::attributes::AttributeStability; use crate::attributes::diagnostic::*; use crate::attributes::prelude::*; -use crate::context::AcceptContext; -use crate::parser::ArgParser; use crate::target_checking::AllowedTargets; use crate::template; @@ -15,32 +13,22 @@ pub(crate) struct OnTypeErrorParser { directive: Option<(Span, Directive)>, } -impl OnTypeErrorParser { - fn parse<'sess>(&mut self, cx: &mut AcceptContext<'_, 'sess>, args: &ArgParser, mode: Mode) { - if !cx.features().diagnostic_on_type_error() { - // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs - args.ignore_args(); - return; - } - - let span = cx.attr_span; - self.span = Some(span); - - let Some(items) = parse_list(cx, args, mode) else { return }; - - if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { - merge_directives(cx, &mut self.directive, (span, directive)); - } - } -} - impl AttributeParser for OnTypeErrorParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_type_error], template!(List: &[r#"note = "...""#]), - AttributeStability::Stable, + AttributeStability::Stable, // Unstable, stability checked manually below |this, cx, args| { - this.parse(cx, args, Mode::DiagnosticOnTypeError); + gate_diagnostic_attr!(diagnostic_on_type_error); + + let span = cx.attr_span; + this.span = Some(span); + let mode = Mode::DiagnosticOnTypeError; + let Some(items) = parse_list(cx, args, mode) else { return }; + + if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { + merge_directives(cx, &mut this.directive, (span, directive)); + } }, )]; diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs index bfa26d993b17e..029d971910e5e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs @@ -10,33 +10,23 @@ pub(crate) struct OnUnknownParser { directive: Option<(Span, Directive)>, } -impl OnUnknownParser { - fn parse<'sess>(&mut self, cx: &mut AcceptContext<'_, 'sess>, args: &ArgParser, mode: Mode) { - if let Some(features) = cx.features - && !features.diagnostic_on_unknown() - { - // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs - args.ignore_args(); - return; - } - let span = cx.attr_span; - self.span = Some(span); - - let Some(items) = parse_list(cx, args, mode) else { return }; - - if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { - merge_directives(cx, &mut self.directive, (span, directive)); - }; - } -} - impl AttributeParser for OnUnknownParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_unknown], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), - AttributeStability::Stable, // Unstable, stability checked manually in the parser + AttributeStability::Stable, // Unstable, stability checked manually below |this, cx, args| { - this.parse(cx, args, Mode::DiagnosticOnUnknown); + gate_diagnostic_attr!(diagnostic_on_unknown); + + let span = cx.attr_span; + this.span = Some(span); + let mode = Mode::DiagnosticOnUnknown; + + let Some(items) = parse_list(cx, args, mode) else { return }; + + if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { + merge_directives(cx, &mut this.directive, (span, directive)); + }; }, )]; // "Allowed" for all targets, but noop for all but use statements. diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatched_args.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatched_args.rs index df8cee63506cc..41ed6df43063e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatched_args.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatched_args.rs @@ -14,13 +14,9 @@ impl AttributeParser for OnUnmatchedArgsParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_unmatched_args], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), - AttributeStability::Stable, // Unstable, stability checked manually in the parser + AttributeStability::Stable, // Unstable, stability checked manually below |this, cx, args| { - if !cx.features().diagnostic_on_unmatched_args() { - // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs - args.ignore_args(); - return; - } + gate_diagnostic_attr!(diagnostic_on_unmatched_args); let span = cx.attr_span; this.span = Some(span); diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/opaque.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/opaque.rs index 64c5ed704f8c5..0864b76f0e030 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/opaque.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/opaque.rs @@ -4,6 +4,7 @@ use rustc_hir::attrs::AttributeKind; use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES; use rustc_span::{Span, sym}; +use crate::attributes::diagnostic::gate_diagnostic_attr; use crate::attributes::{AcceptMapping, AttributeParser}; use crate::context::{AcceptContext, FinalizeContext}; use crate::diagnostics::OpaqueDoesNotExpectArgs; @@ -22,11 +23,9 @@ impl AttributeParser for OpaqueParser { ( &[sym::diagnostic, sym::opaque], template!(Word), - AttributeStability::Stable, // Unstable, stability checked manually in the parser + AttributeStability::Stable, // Unstable, stability checked manually below |this, cx, args| { - if !cx.features().diagnostic_opaque() { - return; - } + gate_diagnostic_attr!(diagnostic_opaque); this.parse(cx, args); }, ), diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index f9ebd78580b4c..1e76ab44826a9 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -848,3 +848,32 @@ pub(crate) struct ToolReserved { pub(crate) span: Span, pub(crate) tool: Ident, } + +#[derive(Diagnostic)] +#[diag("unknown diagnostic attribute")] +pub(crate) struct UnknownDiagnosticAttribute { + #[subdiagnostic] + pub typo: Option, +} + +#[derive(Subdiagnostic)] +#[suggestion( + "an attribute with a similar name exists", + style = "verbose", + code = "{typo_name}", + applicability = "machine-applicable" +)] +pub(crate) struct UnknownDiagnosticAttributeTypo { + #[primary_span] + pub span: Span, + pub typo_name: Symbol, +} + +#[derive(Diagnostic)] +#[diag("unknown diagnostic attribute")] +pub(crate) struct UnstableDiagnosticAttribute { + #[note("this is an experimental diagnostic attribute")] + #[help("add `#![feature({$feature})]` to the crate attributes to enable")] + pub nightly_build: bool, + pub feature: Symbol, +} diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index cea549e310476..73108e42d5ab5 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -292,6 +292,7 @@ impl<'sess> AttributeParser<'sess> { self.sess } + #[track_caller] pub(crate) fn features(&self) -> &'sess Features { self.features.expect("features not available at this point in the compiler") } @@ -451,6 +452,8 @@ impl<'sess> AttributeParser<'sess> { if !cx.shared.has_lint_been_emitted.load(Ordering::Relaxed) { cx.shared.cx.check_args_used(attr, &args) } + } else if let [sym::diagnostic, _unknown, ..] = &*parts { + self.unknown_diagnostic_attr(&n.item.path.segments[1], &mut emit_lint); } else { let attr = AttrItem { path: attr_path.clone(), diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 1b58a9aae5abe..bcdb401bc08c3 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -87,9 +87,11 @@ //! [`rustc_passes::check_attr`]: ../rustc_passes/check_attr/index.html // tidy-alphabetical-start +#![expect(internal_features, reason = "rustc_attrs")] #![feature(decl_macro)] #![feature(deref_patterns)] #![feature(iter_intersperse)] +#![feature(rustc_attrs)] #![feature(try_blocks)] #![recursion_limit = "256"] // tidy-alphabetical-end diff --git a/compiler/rustc_resolve/src/diagnostics/mod.rs b/compiler/rustc_resolve/src/diagnostics/mod.rs index cadfab22c8862..9053d45a41191 100644 --- a/compiler/rustc_resolve/src/diagnostics/mod.rs +++ b/compiler/rustc_resolve/src/diagnostics/mod.rs @@ -1503,30 +1503,6 @@ pub(crate) struct RedundantImportVisibility { pub max_vis: String, } -#[derive(Diagnostic)] -#[diag("unknown diagnostic attribute")] -pub(crate) struct UnknownDiagnosticAttribute { - #[subdiagnostic] - pub help: Option, -} - -#[derive(Subdiagnostic)] -pub(crate) enum UnknownDiagnosticAttributeHelp { - #[suggestion( - "an attribute with a similar name exists", - style = "verbose", - code = "{typo_name}", - applicability = "machine-applicable" - )] - Typo { - #[primary_span] - span: Span, - typo_name: Symbol, - }, - #[help("add `#![feature({$feature})]` to the crate attributes to enable")] - UseFeature { feature: Symbol }, -} - // FIXME: Make this properly translatable. pub(crate) struct Ambiguity { pub ident: Ident, diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 1e9d60ca21551..812b769561054 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -25,11 +25,9 @@ use rustc_middle::ty::{RegisteredTools, TyCtxt}; use rustc_session::Session; use rustc_session::diagnostics::feature_err; use rustc_session::lint::builtin::{ - LEGACY_DERIVE_HELPERS, OUT_OF_SCOPE_MACRO_CALLS, UNKNOWN_DIAGNOSTIC_ATTRIBUTES, - UNUSED_MACRO_RULES, UNUSED_MACROS, + LEGACY_DERIVE_HELPERS, OUT_OF_SCOPE_MACRO_CALLS, UNUSED_MACRO_RULES, UNUSED_MACROS, }; use rustc_span::def_id::ModId; -use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::edition::Edition; use rustc_span::hygiene::{self, AstPass, ExpnData, ExpnKind, LocalExpnId, MacroKind}; use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; @@ -742,60 +740,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { feature_err(&self.tcx.sess, sym::custom_inner_attributes, path.span, msg).emit(); } - const DIAGNOSTIC_ATTRIBUTES: &[(Symbol, Option)] = &[ - (sym::on_unimplemented, None), - (sym::do_not_recommend, None), - (sym::on_move, Some(sym::diagnostic_on_move)), - (sym::on_const, Some(sym::diagnostic_on_const)), - (sym::on_unknown, Some(sym::diagnostic_on_unknown)), - (sym::on_unmatched_args, Some(sym::diagnostic_on_unmatched_args)), - (sym::on_type_error, Some(sym::diagnostic_on_type_error)), - (sym::opaque, Some(sym::diagnostic_opaque)), - ]; - - if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) - && let [namespace, attribute, ..] = &*path.segments - && namespace.ident.name == sym::diagnostic - && !DIAGNOSTIC_ATTRIBUTES.iter().any(|(attr, feature)| { - attribute.ident.name == *attr && feature.is_none_or(|f| self.features.enabled(f)) - }) - { - let name = attribute.ident.name; - let span = attribute.span(); - - let help = 'help: { - if self.tcx.sess.is_nightly_build() { - for (attr, feature) in DIAGNOSTIC_ATTRIBUTES { - if let Some(feature) = *feature - && *attr == name - { - break 'help Some( - diagnostics::UnknownDiagnosticAttributeHelp::UseFeature { feature }, - ); - } - } - } - - let candidates = DIAGNOSTIC_ATTRIBUTES - .iter() - .filter_map(|(attr, feature)| { - feature.is_none_or(|f| self.features.enabled(f)).then_some(*attr) - }) - .collect::>(); - - find_best_match_for_name(&candidates, name, None).map(|typo_name| { - diagnostics::UnknownDiagnosticAttributeHelp::Typo { span, typo_name } - }) - }; - - self.tcx.sess.psess.buffer_lint( - UNKNOWN_DIAGNOSTIC_ATTRIBUTES, - span, - node_id, - diagnostics::UnknownDiagnosticAttribute { help }, - ); - } - Ok((ext, res)) } diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr index 593120edd1700..fa42547a6b652 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr @@ -1,9 +1,10 @@ warning: unknown diagnostic attribute - --> $DIR/feature-gate-diagnostic-on-move.rs:5:15 + --> $DIR/feature-gate-diagnostic-on-move.rs:5:1 | LL | #[diagnostic::on_move(message = "Foo")] - | ^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: this is an experimental diagnostic attribute = help: add `#![feature(diagnostic_on_move)]` to the crate attributes to enable = note: `#[warn(unknown_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error-malformed-args.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error-malformed-args.stderr index c8b4aac78d6e1..3964152f3d4cf 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error-malformed-args.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error-malformed-args.stderr @@ -1,9 +1,10 @@ warning: unknown diagnostic attribute - --> $DIR/feature-gate-diagnostic-on-type-error-malformed-args.rs:5:15 + --> $DIR/feature-gate-diagnostic-on-type-error-malformed-args.rs:5:1 | LL | #[diagnostic::on_type_error(unknown = "")] - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: this is an experimental diagnostic attribute = help: add `#![feature(diagnostic_on_type_error)]` to the crate attributes to enable = note: `#[warn(unknown_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.rs b/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.rs index b17e6b57ef7da..355af18939e83 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.rs +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.rs @@ -2,6 +2,7 @@ #[diagnostic::on_type_error(note = "custom on_type_error note: expected {Expected}, found {Found}")] //~^ WARN unknown diagnostic attribute +//~| NOTE this is an experimental diagnostic attribute //~| NOTE `#[warn(unknown_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default #[derive(Debug)] struct Foo(T); diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.stderr index 72f5cd932124f..f58f379aa13d0 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.stderr @@ -1,14 +1,15 @@ warning: unknown diagnostic attribute - --> $DIR/feature-gate-diagnostic-on-type-error.rs:3:15 + --> $DIR/feature-gate-diagnostic-on-type-error.rs:3:1 | LL | #[diagnostic::on_type_error(note = "custom on_type_error note: expected {Expected}, found {Found}")] - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: this is an experimental diagnostic attribute = help: add `#![feature(diagnostic_on_type_error)]` to the crate attributes to enable = note: `#[warn(unknown_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default error[E0308]: mismatched types - --> $DIR/feature-gate-diagnostic-on-type-error.rs:14:15 + --> $DIR/feature-gate-diagnostic-on-type-error.rs:15:15 | LL | takes_foo(foo); | --------- ^^^ expected `Foo`, found `Foo` @@ -18,7 +19,7 @@ LL | takes_foo(foo); = note: expected struct `Foo` found struct `Foo` note: function defined here - --> $DIR/feature-gate-diagnostic-on-type-error.rs:9:4 + --> $DIR/feature-gate-diagnostic-on-type-error.rs:10:4 | LL | fn takes_foo(_: Foo) {} | ^^^^^^^^^ ----------- diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr index 6e9d35a09821b..d9a22ccf9eaa6 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr @@ -7,11 +7,12 @@ LL | use std::vec::NotExisting; | no `NotExisting` in `vec` error: unknown diagnostic attribute - --> $DIR/feature-gate-diagnostic-on-unknown.rs:3:15 + --> $DIR/feature-gate-diagnostic-on-unknown.rs:3:1 | LL | #[diagnostic::on_unknown(message = "Tada")] - | ^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: this is an experimental diagnostic attribute = help: add `#![feature(diagnostic_on_unknown)]` to the crate attributes to enable note: the lint level is defined here --> $DIR/feature-gate-diagnostic-on-unknown.rs:1:9 diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-opaque.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-opaque.stderr index 90426a1324e81..3de2036717909 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-opaque.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-opaque.stderr @@ -1,9 +1,10 @@ error: unknown diagnostic attribute - --> $DIR/feature-gate-diagnostic-opaque.rs:5:15 + --> $DIR/feature-gate-diagnostic-opaque.rs:5:1 | LL | #[diagnostic::opaque] - | ^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^ | + = note: this is an experimental diagnostic attribute = help: add `#![feature(diagnostic_opaque)]` to the crate attributes to enable note: the lint level is defined here --> $DIR/feature-gate-diagnostic-opaque.rs:3:9 @@ -12,11 +13,12 @@ LL | #![deny(unknown_diagnostic_attributes)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unknown diagnostic attribute - --> $DIR/feature-gate-diagnostic-opaque.rs:11:15 + --> $DIR/feature-gate-diagnostic-opaque.rs:11:1 | LL | #[diagnostic::opaque] - | ^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^ | + = note: this is an experimental diagnostic attribute = help: add `#![feature(diagnostic_opaque)]` to the crate attributes to enable error: aborting due to 2 previous errors From cd8a97b4b21a9c2f8fa1ec9eed2cc6c83c489ddb Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:38:32 +0000 Subject: [PATCH 15/57] Handle LLVM 21 in memchr result codegen test LLVM 21 preserves the bounds assumption but does not eliminate the aggregate phi that LLVM 22 removes. Check each version's supported optimization and restore the shared postcondition so direct callers can eliminate bounds checks. --- library/core/src/slice/memchr.rs | 14 +++----------- .../lib-optimizations/memchr-result.rs | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 68826ecac31f3..fb99e86139d7e 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,18 +24,10 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - if text.len() < 2 * USIZE_BYTES { - let result = memchr_naive(x, text); - if let Some(index) = result { - // SAFETY: `memchr_naive` only returns an index from within `text`. - unsafe { crate::hint::assert_unchecked(index < text.len()) }; - } - return result; - } - - let result = memchr_aligned(x, text); + let result = + if text.len() < 2 * USIZE_BYTES { memchr_naive(x, text) } else { memchr_aligned(x, text) }; if let Some(index) = result { - // SAFETY: `memchr_aligned` only returns an index from within `text`. + // SAFETY: Both implementations only return an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; } result diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs index 77abc33adde83..beeab470c08af 100644 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -1,22 +1,34 @@ // Ensure `memchr` communicates that a returned index is in bounds. //@ compile-flags: -Copt-level=3 -Zinline-mir=false //@ only-x86_64 +//@ revisions: llvm-old llvm-new +//@ [llvm-old] max-llvm-major-version: 21 +//@ [llvm-new] min-llvm-version: 22 #![crate_type = "lib"] #![feature(slice_internals)] extern crate core; -use core::slice::memchr::memrchr; +use core::slice::memchr::{memchr, memrchr}; // CHECK-LABEL: @find_char #[no_mangle] pub fn find_char(haystack: &str, needle: char) -> Option { - // CHECK-NOT: phi { i64, i64 } + // llvm-old: call void @llvm.assume + // llvm-new-NOT: phi { i64, i64 } // CHECK: ret { i64, i64 } haystack.find(needle) } +// CHECK-LABEL: @find_byte +#[no_mangle] +pub fn find_byte(haystack: &[u8], needle: u8) -> Option { + // llvm-new-NOT: panic_bounds_check + // CHECK: ret { i1, i8 } + memchr(needle, haystack).map(|index| haystack[index]) +} + // CHECK-LABEL: @rfind_byte #[no_mangle] pub fn rfind_byte(haystack: &[u8], needle: u8) -> Option { From 6ea57afda2eb91a53011b4a8d6ab481c674322ed Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 6 Aug 2026 15:57:31 +1000 Subject: [PATCH 16/57] Snapshot test for `./x fix compiler` --- src/bootstrap/src/core/builder/tests.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index dddb70b3fd468..68f0419e5b731 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -3086,6 +3086,15 @@ mod snapshot { [run] rustc 0 -> miri 1 "); } + + #[test] + fn fix_compiler() { + let ctx = TestCtx::new(); + insta::assert_snapshot!(ctx.config("fix").path("compiler").render_steps(), @r" + [build] llvm + [check] rustc 0 -> rustc 1 (74 crates) + "); + } } struct ExecutedSteps { From 938bf98d284b6db5777568b188f8b0a0f97882ba Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 5 Aug 2026 21:14:26 +1000 Subject: [PATCH 17/57] Inline and remove constructors from `check::Rustc` These extra layers of indirection are more confusing than helpful. --- src/bootstrap/src/core/build_steps/check.rs | 35 +++++++-------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index 3c4815993b786..a80a34697799d 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -225,11 +225,11 @@ impl Step for PrepareRustcRmetaSysroot { fn run(self, builder: &Builder<'_>) -> Self::Output { // Check rustc - let stamp = builder.ensure(Rustc::from_build_compiler( - self.build_compiler.clone(), - self.target, - vec![], - )); + let stamp = builder.ensure(Rustc { + build_compiler: self.build_compiler.clone(), + target: self.target, + crates: vec![], + }); let build_compiler = self.build_compiler.build_compiler(); @@ -285,8 +285,9 @@ impl Step for PrepareStdRmetaSysroot { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Rustc { /// Compiler that will check this rustc. - pub build_compiler: CompilerForCheck, - pub target: TargetSelection, + build_compiler: CompilerForCheck, + target: TargetSelection, + /// Whether to build only a subset of crates. /// /// This shouldn't be used from other steps; see the comment on [`compile::Rustc`]. @@ -295,21 +296,6 @@ pub struct Rustc { crates: Vec, } -impl Rustc { - pub fn new(builder: &Builder<'_>, target: TargetSelection, crates: Vec) -> Self { - let build_compiler = prepare_compiler_for_check(builder, target, Mode::Rustc); - Self::from_build_compiler(build_compiler, target, crates) - } - - fn from_build_compiler( - build_compiler: CompilerForCheck, - target: TargetSelection, - crates: Vec, - ) -> Self { - Self { build_compiler, target, crates } - } -} - impl CommandLineStep for Rustc { type Output = BuildStamp; const IS_HOST: bool = true; @@ -323,8 +309,11 @@ impl CommandLineStep for Rustc { } fn make_run(run: RunConfig<'_>) { + let target = run.target; + let build_compiler = prepare_compiler_for_check(run.builder, target, Mode::Rustc); let crates = run.make_run_crates(Alias::Compiler); - run.builder.ensure(Rustc::new(run.builder, run.target, crates)); + + run.builder.ensure(Rustc { build_compiler, target, crates }); } /// Check the compiler. From b344260f62752cc6715bd77e332e511f900b2899 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 5 Aug 2026 21:25:23 +1000 Subject: [PATCH 18/57] Store and use an explicit CheckKind in `check::Rustc` This has the pleasant side-effect of making `./x fix compiler` actually work, without breaking `./x clippy` (which relied on the hardcoded `Kind::Check`). --- src/bootstrap/src/core/build_steps/check.rs | 62 ++++++++++++++++----- src/bootstrap/src/core/builder/mod.rs | 2 +- src/bootstrap/src/core/builder/tests.rs | 2 +- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index a80a34697799d..f3bc9840f8e45 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -20,6 +20,23 @@ use crate::core::config::TargetSelection; use crate::utils::build_stamp::{self, BuildStamp}; use crate::{CodegenBackendKind, Compiler, Mode, Subcommand, t}; +/// Allows individual check-step instances to keep track of whether they +/// represent `cargo check` or `cargo fix`, independently of [`Builder::kind`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum CheckKind { + Check, + Fix, +} + +impl CheckKind { + fn to_kind(self) -> Kind { + match self { + CheckKind::Check => Kind::Check, + CheckKind::Fix => Kind::Fix, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Std { /// Compiler that will check this std. @@ -225,11 +242,7 @@ impl Step for PrepareRustcRmetaSysroot { fn run(self, builder: &Builder<'_>) -> Self::Output { // Check rustc - let stamp = builder.ensure(Rustc { - build_compiler: self.build_compiler.clone(), - target: self.target, - crates: vec![], - }); + let stamp = Rustc::check_rustc_for_preparing_sysroot(builder, &self); let build_compiler = self.build_compiler.build_compiler(); @@ -284,6 +297,8 @@ impl Step for PrepareStdRmetaSysroot { /// Checks rustc using `build_compiler`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Rustc { + check_kind: CheckKind, + /// Compiler that will check this rustc. build_compiler: CompilerForCheck, target: TargetSelection, @@ -296,6 +311,21 @@ pub struct Rustc { crates: Vec, } +impl Rustc { + fn check_rustc_for_preparing_sysroot( + builder: &Builder<'_>, + prepare: &PrepareRustcRmetaSysroot, + ) -> BuildStamp { + builder.ensure(Rustc { + // We specifically want `cargo check`, not the current bootstrap subcommand. + check_kind: CheckKind::Check, + build_compiler: prepare.build_compiler.clone(), + target: prepare.target, + crates: vec![], + }) + } +} + impl CommandLineStep for Rustc { type Output = BuildStamp; const IS_HOST: bool = true; @@ -309,11 +339,17 @@ impl CommandLineStep for Rustc { } fn make_run(run: RunConfig<'_>) { + let check_kind = match run.builder.kind { + Kind::Check => CheckKind::Check, + Kind::Fix => CheckKind::Fix, + kind => panic!("unexpected kind for `check::Rustc`: {kind:?}"), + }; + let target = run.target; let build_compiler = prepare_compiler_for_check(run.builder, target, Mode::Rustc); let crates = run.make_run_crates(Alias::Compiler); - run.builder.ensure(Rustc { build_compiler, target, crates }); + run.builder.ensure(Rustc { check_kind, build_compiler, target, crates }); } /// Check the compiler. @@ -333,7 +369,7 @@ impl CommandLineStep for Rustc { Mode::Rustc, SourceType::InTree, target, - Kind::Check, + self.check_kind.to_kind(), ); rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates); @@ -347,7 +383,7 @@ impl CommandLineStep for Rustc { } let _guard = builder.msg( - Kind::Check, + self.check_kind.to_kind(), format_args!("compiler artifacts{}", crate_description(&self.crates)), Mode::Rustc, self.build_compiler.build_compiler(), @@ -370,13 +406,11 @@ impl CommandLineStep for Rustc { } fn metadata(&self) -> Option { - let metadata = StepMetadata::check("rustc", self.target) + let mut metadata = StepMetadata::new("rustc", self.target, self.check_kind.to_kind()) .built_by(self.build_compiler.build_compiler()); - let metadata = if self.crates.is_empty() { - metadata - } else { - metadata.with_metadata(format!("({} crates)", self.crates.len())) - }; + if !self.crates.is_empty() { + metadata = metadata.with_metadata(format!("({} crates)", self.crates.len())); + } Some(metadata) } } diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 603ef65854cf6..ccb6efb8bd723 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -229,7 +229,7 @@ impl StepMetadata { Self::new(name, target, Kind::Run) } - fn new(name: &str, target: TargetSelection, kind: Kind) -> Self { + pub fn new(name: &str, target: TargetSelection, kind: Kind) -> Self { Self { name: name.to_string(), kind, target, built_by: None, stage: None, metadata: None } } diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 68f0419e5b731..57f50d981d1c4 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -3092,7 +3092,7 @@ mod snapshot { let ctx = TestCtx::new(); insta::assert_snapshot!(ctx.config("fix").path("compiler").render_steps(), @r" [build] llvm - [check] rustc 0 -> rustc 1 (74 crates) + [fix] rustc 0 -> rustc 1 (74 crates) "); } } From fd7e845b9e00f14391e098bd6c146024b04993e4 Mon Sep 17 00:00:00 2001 From: Marius Melzer Date: Fri, 9 Jan 2026 18:07:12 +0100 Subject: [PATCH 19/57] Add documentation and maintainer for L4Re target --- src/doc/rustc/src/SUMMARY.md | 1 + src/doc/rustc/src/platform-support.md | 3 +- src/doc/rustc/src/platform-support/l4re.md | 63 ++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/doc/rustc/src/platform-support/l4re.md diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index ca5890840581c..bedfa65ac894d 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -84,6 +84,7 @@ - [avr-none](platform-support/avr-none.md) - [\*-espidf](platform-support/esp-idf.md) - [\*-unknown-fuchsia](platform-support/fuchsia.md) + - [\*-unknown-l4re](platform-support/l4re.md) - [\*-unknown-trusty](platform-support/trusty.md) - [\*-kmc-solid_\*](platform-support/kmc-solid.md) - [csky-unknown-linux-gnuabiv2\*](platform-support/csky-unknown-linux-gnuabiv2.md) diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 81e843263487c..c8ae02b091034 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -273,6 +273,7 @@ target | std | host | notes [`aarch64-unknown-helenos`](platform-support/helenos.md) | ✓ | | ARM64 HelenOS [`aarch64-unknown-hermit`](platform-support/hermit.md) | ✓ | | ARM64 Hermit [`aarch64-unknown-illumos`](platform-support/illumos.md) | ✓ | ✓ | ARM64 illumos +[`aarch64-unknown-l4re-uclibc`](platform-support/l4re.md) | ✓ | | ARM64 L4Re with uclibc `aarch64-unknown-linux-gnu_ilp32` | ✓ | ✓ | ARM64 Linux (ILP32 ABI) [`aarch64-unknown-linux-pauthtest`](platform-support/aarch64-unknown-linux-pauthtest.md) | ✓ | ✓ | ARM64 PAC ELF ABI [`aarch64-unknown-managarm-mlibc`](platform-support/managarm.md) | ? | | ARM64 Managarm @@ -459,7 +460,7 @@ target | std | host | notes [`x86_64-unknown-hermit`](platform-support/hermit.md) | ✓ | | x86_64 Hermit [`x86_64-unknown-helenos`](platform-support/helenos.md) | ✓ | | x86_64 (amd64) HelenOS [`x86_64-unknown-hurd-gnu`](platform-support/hurd.md) | ✓ | ✓ | 64-bit GNU/Hurd -`x86_64-unknown-l4re-uclibc` | ? | | +[`x86_64-unknown-l4re-uclibc`](platform-support/l4re.md) | ✓ | | x86_64 L4Re with uclibc [`x86_64-unknown-linux-none`](platform-support/x86_64-unknown-linux-none.md) | * | | 64-bit Linux with no libc [`x86_64-unknown-managarm-mlibc`](platform-support/managarm.md) | ? | | x86_64 Managarm [`x86_64-unknown-motor`](platform-support/motor.md) | ✓ | | x86_64 Motor OS diff --git a/src/doc/rustc/src/platform-support/l4re.md b/src/doc/rustc/src/platform-support/l4re.md new file mode 100644 index 0000000000000..56044319dc77a --- /dev/null +++ b/src/doc/rustc/src/platform-support/l4re.md @@ -0,0 +1,63 @@ +# `*-l4re-uclibc` + +**Tier: 3** + +[L4Re] is an open source, microkernel-based operating system and hypervisor. + +Target triplets available so far: + +- x86_64-unknown-l4re-uclibc +- aarch64-unknown-l4re-uclibc + +## Target maintainers + +- Marius Melzer ([@farao](https://github.com/farao)) + +## Requirements + +The L4Re targets are cross-compiled from a host environment, commonly Linux. +See [Getting Started] for options to set up L4Re. + +The L4Re sources can be found in the [Github Repos]. + +## Building an L4Re Rust Toolchain + +Configure one or several of the above L4Re targets and also add the host triple +in config.toml and build Rust as documented. Start off the toolchain by copying +`build/host/stage2/` to a self-chosen location. + +For each target, build an L4Re sysroot directory by running `make sysroot` in +the L4Re build directory. Copy the content of `sysroot/usr/lib/` into the +`self-contained` directory of the respective target in the Rust Toolchain +directory tree. + +Use rustup to install the L4Re Rust Toolchain locally: + +```sh +rustup toolchain link l4re +``` + +Now use the toolchain via a cargo (or directly a rustc) installed via `rustup`: + +```sh +cargo +l4re build --target +``` + +or + +```sh +rustc +l4re --target +``` + +## Run Rust Programs on L4Re + +You can run an L4Re application written in Rust just like any other externally +built (meaning not build with the L4Re build system) L4Re binary. A good option +is to build an L4Re image and add the application binary to the image and run it +via the ned script. The image can then be put on hardware or run on Qemu. + +See [l4re.org](https://l4re.org) for more information. + +[L4Re]: https://l4re.org +[Getting Started]: https://l4re.org/getting_started +[Github Repos]: https://github.com/L4Re From 5bb8d31122d3f1a13cc338804f2e032be8f04c8d Mon Sep 17 00:00:00 2001 From: Marius Melzer Date: Fri, 9 Jan 2026 17:57:53 +0100 Subject: [PATCH 20/57] Add aarch64 architecture for L4Re target --- compiler/rustc_target/src/spec/mod.rs | 1 + .../targets/aarch64_unknown_l4re_uclibc.rs | 28 +++++++++++++++++++ src/bootstrap/src/core/sanity.rs | 1 + tests/assembly-llvm/targets/targets-elf.rs | 3 ++ 4 files changed, 33 insertions(+) create mode 100644 compiler/rustc_target/src/spec/targets/aarch64_unknown_l4re_uclibc.rs diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 1f17173953643..25465fc29f945 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -1569,6 +1569,7 @@ supported_targets! { ("avr-none", avr_none), + ("aarch64-unknown-l4re-uclibc", aarch64_unknown_l4re_uclibc), ("x86_64-unknown-l4re-uclibc", x86_64_unknown_l4re_uclibc), ("aarch64-unknown-redox", aarch64_unknown_redox), diff --git a/compiler/rustc_target/src/spec/targets/aarch64_unknown_l4re_uclibc.rs b/compiler/rustc_target/src/spec/targets/aarch64_unknown_l4re_uclibc.rs new file mode 100644 index 0000000000000..bca1195ddad72 --- /dev/null +++ b/compiler/rustc_target/src/spec/targets/aarch64_unknown_l4re_uclibc.rs @@ -0,0 +1,28 @@ +use crate::spec::{Arch, Cc, LinkerFlavor, Target, TargetOptions, base}; + +pub(crate) fn target() -> Target { + let mut base = base::l4re::opts(); + + let extra_link_args = &["-zmax-page-size=0x1000", "-zcommon-page-size=0x1000"]; + base.add_pre_link_args(LinkerFlavor::Unix(Cc::Yes), extra_link_args); + base.add_pre_link_args(LinkerFlavor::Unix(Cc::No), extra_link_args); + + Target { + llvm_target: "aarch64-unknown-l4re-uclibc".into(), + metadata: crate::spec::TargetMetadata { + description: Some("Arm64 L4Re".into()), + tier: Some(3), + host_tools: Some(false), + std: Some(true), + }, + pointer_width: 64, + data_layout: "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32".into(), + arch: Arch::AArch64, + options: TargetOptions { + features: "+v8a".into(), + mcount: "__mcount".into(), + max_atomic_width: Some(128), + ..base + } + } +} diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index 400d0715a4738..e4942a8ce669f 100644 --- a/src/bootstrap/src/core/sanity.rs +++ b/src/bootstrap/src/core/sanity.rs @@ -34,6 +34,7 @@ pub struct Finder { /// when the newly-bumped stage 0 compiler now knows about the formerly-missing targets. const STAGE0_MISSING_TARGETS: &[&str] = &[ // just a dummy comment so the list doesn't get onelined + "aarch64-unknown-l4re-uclibc", ]; /// Minimum version threshold for libstdc++ required when using prebuilt LLVM diff --git a/tests/assembly-llvm/targets/targets-elf.rs b/tests/assembly-llvm/targets/targets-elf.rs index 0f9f68cfde787..49bced1dd5bd2 100644 --- a/tests/assembly-llvm/targets/targets-elf.rs +++ b/tests/assembly-llvm/targets/targets-elf.rs @@ -46,6 +46,9 @@ //@ revisions: aarch64_unknown_illumos //@ [aarch64_unknown_illumos] compile-flags: --target aarch64-unknown-illumos //@ [aarch64_unknown_illumos] needs-llvm-components: aarch64 +//@ revisions: aarch64_unknown_l4re_uclibc +//@ [aarch64_unknown_l4re_uclibc] compile-flags: --target aarch64-unknown-l4re-uclibc +//@ [aarch64_unknown_l4re_uclibc] needs-llvm-components: aarch64 //@ revisions: aarch64_unknown_linux_gnu //@ [aarch64_unknown_linux_gnu] compile-flags: --target aarch64-unknown-linux-gnu //@ [aarch64_unknown_linux_gnu] needs-llvm-components: aarch64 From 225ca5fe1127509235266e12f0e3524327be2e26 Mon Sep 17 00:00:00 2001 From: Havard Eidnes Date: Thu, 6 Aug 2026 10:27:11 +0000 Subject: [PATCH 21/57] platform-support/netbsd.md: No longer mention 8.x, due to EoL. Also change the pkgsrc-wip link to indicate a more current rust version. To be re-visited again once 9.x reaches end of maintnance and EoL by the end of the current month. --- src/doc/rustc/src/platform-support/netbsd.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc/src/platform-support/netbsd.md b/src/doc/rustc/src/platform-support/netbsd.md index f7b57fff8a1f9..0d060f1de4cf5 100644 --- a/src/doc/rustc/src/platform-support/netbsd.md +++ b/src/doc/rustc/src/platform-support/netbsd.md @@ -24,10 +24,11 @@ are currently defined running NetBSD: | 3 | `sparc64-unknown-netbsd` | [Sun UltraSPARC systems](https://wiki.netbsd.org/ports/sparc64/) | All use the "native" `stdc++` library which goes along with the natively -supplied GNU C++ compiler for the given OS version. Many of the bootstraps -are built for NetBSD 9.x, although some exceptions exist (some -are built for NetBSD 8.x but also work on newer OS versions). -`x86_64-unknown-netbsd` is built for NetBSD 10.x to access a newer gcc. +supplied GNU C++ compiler for the given OS version. Most of the bootstraps +are built for NetBSD 9.x, although some exceptions exist (some are +built for newer NetBSD versions, due to target becoming usable first +with newer versions). `x86_64-unknown-netbsd` is built for NetBSD +10.x to access a newer gcc. ## Target Maintainers @@ -37,7 +38,7 @@ are built for NetBSD 8.x but also work on newer OS versions). Further contacts: -- [NetBSD/pkgsrc-wip's rust](https://github.com/NetBSD/pkgsrc-wip/blob/master/rust188/Makefile) maintainer (see MAINTAINER variable). This package is part of "pkgsrc work-in-progress" and is used for deployment and testing of new versions of rust. Note that we have the convention of having multiple rust versions active in pkgsrc-wip at any one time, so the version number is part of the directory name, and from time to time old versions are culled so this is not a fully "stable" link. +- [NetBSD/pkgsrc-wip's rust](https://github.com/NetBSD/pkgsrc-wip/blob/master/rust197/Makefile) maintainer (see MAINTAINER variable). This package is part of "pkgsrc work-in-progress" and is used for deployment and testing of new versions of rust. Note that we have the convention of having multiple rust versions active in pkgsrc-wip at any one time, so the version number is part of the directory name, and from time to time old versions are culled so this is not a fully "stable" link. - [NetBSD's pkgsrc lang/rust](https://github.com/NetBSD/pkgsrc/tree/trunk/lang/rust) for the "proper" package in pkgsrc. - [NetBSD's pkgsrc lang/rust-bin](https://github.com/NetBSD/pkgsrc/tree/trunk/lang/rust-bin) which re-uses the bootstrap kit as a binary distribution and therefore avoids the rather protracted native build time of rust itself From 0d75b8c356f894d5b134226c309a3df254eff977 Mon Sep 17 00:00:00 2001 From: im-lunex Date: Thu, 6 Aug 2026 16:21:16 +0600 Subject: [PATCH 22/57] fix ICE in `suggest_add_reference_to_arg` for non-callable items --- .../src/error_reporting/traits/suggestions.rs | 13 +++- tests/ui/structs/ice-missing-field-fn-sig.rs | 14 +++++ .../structs/ice-missing-field-fn-sig.stderr | 61 +++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 tests/ui/structs/ice-missing-field-fn-sig.rs create mode 100644 tests/ui/structs/ice-missing-field-fn-sig.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 2a6e2a539c964..633b23a1f28ca 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -13,7 +13,7 @@ use rustc_errors::{ Applicability, Diag, EmissionGuarantee, MultiSpan, Style, SuggestionStyle, pluralize, struct_span_code_err, }; -use rustc_hir::def::{CtorOf, DefKind, Res}; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{Visitor, VisitorExt}; use rustc_hir::lang_items::LangItem; @@ -1764,7 +1764,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // If we didn't return early here, we would instead suggest `&&str::from("")`. return false; } else if let hir::ExprKind::Call(_, args) = expr.kind { - if let Some(pred) = self + // The `def_id` can point at a struct, which has no fn sig. + if matches!( + self.tcx.def_kind(*def_id), + DefKind::AssocFn | DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) + ) && let Some(pred) = self .tcx .clauses_of(*def_id) .instantiate_identity(self.tcx) @@ -1799,6 +1803,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { c @ ObligationCauseCode::WhereClauseInExpr(def_id, _, hir_id, idx) if let hir::Node::Expr(expr) = self.tcx.hir_node(*hir_id) && let hir::ExprKind::MethodCall(_segment, rcvr, args, ..) = expr.kind + // The `def_id` can also point at the impl, which has no fn sig. + && matches!( + self.tcx.def_kind(*def_id), + DefKind::AssocFn | DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) + ) && let Some(pred) = self .tcx .clauses_of(*def_id) diff --git a/tests/ui/structs/ice-missing-field-fn-sig.rs b/tests/ui/structs/ice-missing-field-fn-sig.rs new file mode 100644 index 0000000000000..9ba48f6a6e795 --- /dev/null +++ b/tests/ui/structs/ice-missing-field-fn-sig.rs @@ -0,0 +1,14 @@ +// A struct literal that's missing fields shouldn't ICE when checking the fn sig. + +trait Context {} +struct Wrapper { + container: &'static C, +} +fn foobar(_: Wrapper<()>) { //~ ERROR the trait bound `(): Context` is not satisfied + foobar(Wrapper { /* missing */ }) +//~^ ERROR the trait bound `(): Context` is not satisfied +//~^^ ERROR missing field `container` in initializer of `Wrapper<_>` +//~^^^ ERROR the trait bound `(): Context` is not satisfied +} + +fn main() {} diff --git a/tests/ui/structs/ice-missing-field-fn-sig.stderr b/tests/ui/structs/ice-missing-field-fn-sig.stderr new file mode 100644 index 0000000000000..7be7886b2d975 --- /dev/null +++ b/tests/ui/structs/ice-missing-field-fn-sig.stderr @@ -0,0 +1,61 @@ +error[E0277]: the trait bound `(): Context` is not satisfied + --> $DIR/ice-missing-field-fn-sig.rs:7:14 + | +LL | fn foobar(_: Wrapper<()>) { + | ^^^^^^^^^^^ the trait `Context` is not implemented for `()` + | +help: this trait has no implementations, consider adding one + --> $DIR/ice-missing-field-fn-sig.rs:3:1 + | +LL | trait Context {} + | ^^^^^^^^^^^^^ +note: required by a bound in `Wrapper` + --> $DIR/ice-missing-field-fn-sig.rs:4:19 + | +LL | struct Wrapper { + | ^^^^^^^ required by this bound in `Wrapper` + +error[E0277]: the trait bound `(): Context` is not satisfied + --> $DIR/ice-missing-field-fn-sig.rs:8:12 + | +LL | foobar(Wrapper { /* missing */ }) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Context` is not implemented for `()` + | +help: this trait has no implementations, consider adding one + --> $DIR/ice-missing-field-fn-sig.rs:3:1 + | +LL | trait Context {} + | ^^^^^^^^^^^^^ +note: required by a bound in `Wrapper` + --> $DIR/ice-missing-field-fn-sig.rs:4:19 + | +LL | struct Wrapper { + | ^^^^^^^ required by this bound in `Wrapper` + +error[E0063]: missing field `container` in initializer of `Wrapper<_>` + --> $DIR/ice-missing-field-fn-sig.rs:8:12 + | +LL | foobar(Wrapper { /* missing */ }) + | ^^^^^^^ missing `container` + +error[E0277]: the trait bound `(): Context` is not satisfied + --> $DIR/ice-missing-field-fn-sig.rs:8:12 + | +LL | foobar(Wrapper { /* missing */ }) + | ^^^^^^^ the trait `Context` is not implemented for `()` + | +help: this trait has no implementations, consider adding one + --> $DIR/ice-missing-field-fn-sig.rs:3:1 + | +LL | trait Context {} + | ^^^^^^^^^^^^^ +note: required by a bound in `Wrapper` + --> $DIR/ice-missing-field-fn-sig.rs:4:19 + | +LL | struct Wrapper { + | ^^^^^^^ required by this bound in `Wrapper` + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0063, E0277. +For more information about an error, try `rustc --explain E0063`. From 140d4cb35cba6c650533246a0c59549992e49b4f Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Thu, 6 Aug 2026 10:56:10 +0000 Subject: [PATCH 23/57] delegation: add support for wrapping of the return value with `From::from` * Add support for wrapping of the return value of delegation * Cleanups * Review: use `make_lang_item_qpath` --- .../src/delegation/generics.rs | 4 +- .../rustc_ast_lowering/src/delegation/mod.rs | 11 +- .../src/delegation/resolution.rs | 51 ++++- compiler/rustc_hir/src/lang_items.rs | 1 + compiler/rustc_middle/src/ty/sty.rs | 9 + library/core/src/convert/mod.rs | 1 + .../pretty/delegation/self-mapping-output.pp | 4 +- .../self-mapping-output-from-wrap-errors.rs | 72 +++++++ ...elf-mapping-output-from-wrap-errors.stderr | 57 +++++ .../self-mapping-output-from-wrap.rs | 199 ++++++++++++++++++ .../self-mapping-output-from-wrap.run.stdout | 10 + 11 files changed, 405 insertions(+), 14 deletions(-) create mode 100644 tests/ui/delegation/self-mapping-output-from-wrap-errors.rs create mode 100644 tests/ui/delegation/self-mapping-output-from-wrap-errors.stderr create mode 100644 tests/ui/delegation/self-mapping-output-from-wrap.rs create mode 100644 tests/ui/delegation/self-mapping-output-from-wrap.run.stdout diff --git a/compiler/rustc_ast_lowering/src/delegation/generics.rs b/compiler/rustc_ast_lowering/src/delegation/generics.rs index 4d9bc09faeecb..911ec5956006d 100644 --- a/compiler/rustc_ast_lowering/src/delegation/generics.rs +++ b/compiler/rustc_ast_lowering/src/delegation/generics.rs @@ -662,10 +662,10 @@ impl<'hir> LoweringContext<'_, 'hir> { p.def_id.to_def_id(), ); - self.create_resolved_path(res, p.name.ident(), p.span) + self.create_resolved_qpath(res, p.name.ident(), p.span) } - pub(super) fn create_resolved_path( + pub(super) fn create_resolved_qpath( &mut self, res: Res, ident: Ident, diff --git a/compiler/rustc_ast_lowering/src/delegation/mod.rs b/compiler/rustc_ast_lowering/src/delegation/mod.rs index d0033ba0e472e..02fd6de314d3a 100644 --- a/compiler/rustc_ast_lowering/src/delegation/mod.rs +++ b/compiler/rustc_ast_lowering/src/delegation/mod.rs @@ -439,7 +439,7 @@ impl<'hir> LoweringContext<'_, 'hir> { }; let ident = Ident::new(kw::SelfUpper, span); - let path = self.create_resolved_path(res, ident, span); + let path = self.create_resolved_qpath(res, ident, span); // FIXME(fn_delegation): add default `..` for all other fields. let initializer = hir::ExprKind::Struct( @@ -454,7 +454,14 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::StructTailExpr::None, ); - self.arena.alloc(self.mk_expr(initializer, span)) + let expr = self.mk_expr(initializer, span); + + let path = self.make_lang_item_qpath(hir::LangItem::FromFn, span, None); + let path = self.arena.alloc(self.mk_expr(hir::ExprKind::Path(path), span)); + + let call = hir::ExprKind::Call(path, self.arena.alloc_slice(&[expr])); + + self.arena.alloc(self.mk_expr(call, span)) } else { self.arena.alloc(call) }; diff --git a/compiler/rustc_ast_lowering/src/delegation/resolution.rs b/compiler/rustc_ast_lowering/src/delegation/resolution.rs index 1d9bcef7ac5a7..dd1b9518e6d7f 100644 --- a/compiler/rustc_ast_lowering/src/delegation/resolution.rs +++ b/compiler/rustc_ast_lowering/src/delegation/resolution.rs @@ -5,10 +5,10 @@ use hir::def::DefKind; use rustc_ast::{self as ast, Delegation, DelegationSource, NodeId}; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_hir as hir; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor}; use rustc_middle::{span_bug, ty}; use rustc_span::def_id::{DefId, LocalDefId}; -use rustc_span::{ErrorGuaranteed, Span, kw}; +use rustc_span::{ErrorGuaranteed, Span}; use crate::delegation::generics::GenericsGenerationResults; use crate::delegation::resolution::resolver::DelegationResolver; @@ -31,7 +31,7 @@ pub(super) struct ParamInfo { pub splatted: Option, } -#[derive(Default)] +#[derive(Default, Debug)] pub(super) struct SigMapping { pub map_return: bool, pub arguments_to_map: FxIndexSet, @@ -254,17 +254,52 @@ impl<'tcx> DelegationResolver<'_, 'tcx> { } if self.can_perform_self_mapping(delegation, parent)? { - // FIXME(fn_delegation): support heuristics for mapping of complex - // return types: `Self` -> `Box>>` - mapping.map_return = sig.output().is_param(0); + /// Finds `Self` generic param only in ADT or references, so we avoid cases like + /// `Self::Item` which will return true if `output.contains(...)` will be used. + struct SelfFinder; + + impl<'tcx> TypeVisitor> for SelfFinder { + type Result = ControlFlow<()>; + + fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result { + match t.kind() { + ty::Adt(_, args) => { + if args + .iter() + .flat_map(|arg| arg.as_type()) + .any(|type_arg| type_arg.is_self_param()) + { + return ControlFlow::Break(()); + } + + t.super_visit_with(self) + } + ty::Ref(_, ref_t, _) => { + if ref_t.is_self_param() { + return ControlFlow::Break(()); + } + + t.super_visit_with(self) + } + _ => ControlFlow::Continue(()), + } + } + } + + impl SelfFinder { + fn contains_self(t: Ty<'_>) -> bool { + t.is_self_param() || t.visit_with(&mut SelfFinder).is_break() + } + } + + mapping.map_return = SelfFinder::contains_self(sig.output()); - let self_param = Ty::new_param(self.tcx(), 0, kw::SelfUpper); let arguments_to_map = sig .inputs() .iter() .enumerate() .skip(1) // Already checked above. - .filter_map(|(idx, param)| param.contains(self_param).then_some(idx)); + .filter_map(|(idx, ¶m)| SelfFinder::contains_self(param).then_some(idx)); mapping.arguments_to_map.extend(arguments_to_map); } diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index e6e0b3726552f..fe1ca75d30cf7 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -456,6 +456,7 @@ language_item_table! { // Used to fallback `{float}` to `f32` when `f32: From<{float}>` From, sym::From, from_trait, Target::Trait, GenericRequirement::Exact(1); + FromFn, sym::from, from_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; } /// The requirement imposed on the generics of a lang item diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index e768c75961937..0c0e3d87c9f20 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -1191,6 +1191,15 @@ impl<'tcx> Ty<'tcx> { matches!(self.kind(), Adt(..)) } + #[inline] + pub fn is_self_param(self) -> bool { + if let Param(param) = self.kind() { + param.index == 0 && param.name == kw::SelfUpper + } else { + false + } + } + #[inline] pub fn is_ref(self) -> bool { matches!(self.kind(), Ref(..)) diff --git a/library/core/src/convert/mod.rs b/library/core/src/convert/mod.rs index ae8458c199503..912623b73050e 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -591,6 +591,7 @@ pub const trait From: Sized { #[rustc_diagnostic_item = "from_fn"] #[must_use] #[stable(feature = "rust1", since = "1.0.0")] + #[lang = "from"] fn from(value: T) -> Self; } diff --git a/tests/pretty/delegation/self-mapping-output.pp b/tests/pretty/delegation/self-mapping-output.pp index 84e98d6e97b06..5bce43315e1d0 100644 --- a/tests/pretty/delegation/self-mapping-output.pp +++ b/tests/pretty/delegation/self-mapping-output.pp @@ -24,7 +24,7 @@ struct W(S); impl Trait for W { #[attr = Inline(Hint)] - fn method(self: _) -> _ { Self { 0: Trait::method(self.0) } } + fn method(self: _) -> _ { from(Self { 0: Trait::method(self.0) }) } #[attr = Inline(Hint)] fn r#static() -> _ { Trait::r#static() } //~^ WARN: function cannot return without recursing [unconditional_recursion] @@ -34,7 +34,7 @@ impl W { #[attr = Inline(Hint)] - fn method(self: _) -> _ { Self { 0: Trait::method(self.0) } } + fn method(self: _) -> _ { from(Self { 0: Trait::method(self.0) }) } #[attr = Inline(Hint)] fn r#static() -> _ { Trait::r#static() } #[attr = Inline(Hint)] diff --git a/tests/ui/delegation/self-mapping-output-from-wrap-errors.rs b/tests/ui/delegation/self-mapping-output-from-wrap-errors.rs new file mode 100644 index 0000000000000..6ef2a4b72559c --- /dev/null +++ b/tests/ui/delegation/self-mapping-output-from-wrap-errors.rs @@ -0,0 +1,72 @@ +#![feature(fn_delegation)] + +mod pin_box_self { + use std::pin::Pin; + + trait MyAdd { + fn add(self, other: Self) -> Pin>; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Pin> { + Pin::new(Box::new(self + other)) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Pin>); + + reuse impl MyAdd for W { + //~^ ERROR: the trait bound `Pin>: From` is not satisfied + *self.0 + } +} + +mod many_froms { + use std::sync::Arc; + use std::rc::Rc; + + trait MyAdd { + fn add(self, other: Self) -> Box>>>>>; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Box>>>>> { + Box::new(Box::new(Box::new(Arc::new(Box::new(Rc::new(self + other)))))) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Box>>>>>); + + reuse impl MyAdd for W { + //~^ ERROR: the trait bound `Box>>>>>: From` is not satisfied + ******self.0 + } +} + +mod many_froms_2 { + use std::sync::Arc; + use std::rc::Rc; + + trait MyAdd { + fn add(self, other: Self) -> Box>>>>; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Box>>>> { + Box::new(Arc::new(Rc::new(Box::new(Rc::new(self + other))))) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Box>>>>); + + reuse impl MyAdd for W { + //~^ ERROR: the trait bound `Box>>>>: From` is not satisfied + *****self.0 + } +} + +fn main() { +} diff --git a/tests/ui/delegation/self-mapping-output-from-wrap-errors.stderr b/tests/ui/delegation/self-mapping-output-from-wrap-errors.stderr new file mode 100644 index 0000000000000..d6290bc220966 --- /dev/null +++ b/tests/ui/delegation/self-mapping-output-from-wrap-errors.stderr @@ -0,0 +1,57 @@ +error[E0277]: the trait bound `Pin>: From` is not satisfied + --> $DIR/self-mapping-output-from-wrap-errors.rs:19:5 + | +LL | / reuse impl MyAdd for W { +LL | | +LL | | *self.0 +LL | | } + | |_____^ the trait `From` is not implemented for `Pin>` + | +help: the trait `From` is not implemented for `Pin>` + but trait `From>` is implemented for it + --> $SRC_DIR/alloc/src/boxed/convert.rs:LL:COL + = help: for that trait implementation, expected `Box`, found `pin_box_self::W` + +error[E0277]: the trait bound `Box>>>>>: From` is not satisfied + --> $DIR/self-mapping-output-from-wrap-errors.rs:42:5 + | +LL | / reuse impl MyAdd for W { +LL | | +LL | | ******self.0 +LL | | } + | |_____^ the trait `From` is not implemented for `Box>>>>>` + | + = help: the following other types implement trait `From`: + `Box` implements `From>` + `Box` implements `From<&CStr>` + `Box` implements `From<&mut CStr>` + `Box` implements `From` + `Box` implements `From>` + `Box` implements `From<&OsStr>` + `Box` implements `From<&mut OsStr>` + `Box` implements `From>` + and 25 others + +error[E0277]: the trait bound `Box>>>>: From` is not satisfied + --> $DIR/self-mapping-output-from-wrap-errors.rs:65:5 + | +LL | / reuse impl MyAdd for W { +LL | | +LL | | *****self.0 +LL | | } + | |_____^ the trait `From` is not implemented for `Box>>>>` + | + = help: the following other types implement trait `From`: + `Box` implements `From>` + `Box` implements `From<&CStr>` + `Box` implements `From<&mut CStr>` + `Box` implements `From` + `Box` implements `From>` + `Box` implements `From<&OsStr>` + `Box` implements `From<&mut OsStr>` + `Box` implements `From>` + and 25 others + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/delegation/self-mapping-output-from-wrap.rs b/tests/ui/delegation/self-mapping-output-from-wrap.rs new file mode 100644 index 0000000000000..2dfff2882fbc6 --- /dev/null +++ b/tests/ui/delegation/self-mapping-output-from-wrap.rs @@ -0,0 +1,199 @@ +//@ run-pass +//@ check-run-results + +#![feature(fn_delegation)] + +mod simple_self { + trait MyAdd { + fn add(self, other: Self) -> Self; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> usize { + self + other + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(usize); + + reuse impl MyAdd for W { + println!("simple_self {self:?}"); + self.0 + } + + pub fn check() { + assert_eq!(W(1).add(W(2)), W(3)) + } +} + +mod box_self { + trait MyAdd { + fn add(self, other: Self) -> Box; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Box { + Box::new(self + other) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Box); + + reuse impl MyAdd for W { + println!("box_self {self:?}"); + *self.0 + } + + pub fn check() { + fn w(x: usize) -> W { + W(Box::new(x)) + } + + assert_eq!(w(1).add(w(2)), Box::new(w(3))) + } +} + +mod rc_self { + use std::rc::Rc; + + trait MyAdd { + fn add(self, other: Self) -> Rc; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Rc { + Rc::new(self + other) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Rc); + + reuse impl MyAdd for W { + println!("rc_self {self:?}"); + *self.0 + } + + pub fn check() { + fn w(x: usize) -> W { + W(Rc::new(x)) + } + + assert_eq!(w(1).add(w(2)), Rc::new(w(3))) + } +} + +mod arc_self { + use std::sync::Arc; + + trait MyAdd { + fn add(self, other: Self) -> Arc; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Arc { + Arc::new(self + other) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Arc); + + reuse impl MyAdd for W { + println!("arc_self {self:?}"); + *self.0 + } + + pub fn check() { + fn w(x: usize) -> W { + W(Arc::new(x)) + } + + assert_eq!(w(1).add(w(2)), Arc::new(w(3))) + } +} + +mod custom_froms { + #[derive(Debug)] + struct S1 { + a: A, + } + + impl From for S1 { + fn from(a: A) -> S1 { + S1 { a } + } + } + + #[derive(Debug)] + struct S2 { + t: T, + } + + impl From for S2 { + fn from(t: T) -> S2 { + S2 { t } + } + } + + #[derive(Debug)] + struct S3<'a, const C: usize, T, U, const B: bool> { + t: T, + pd: std::marker::PhantomData<&'a [(usize, U); C]> + } + + impl<'a, const C: usize, T, const B: bool> From for S3<'a, C, T, (), B> { + fn from(t: T) -> S3<'a, C, T, (), B> { + S3 { + t, + pd: std::marker::PhantomData::<&'a [(usize, ()); C]>, + } + } + } + + trait MyAdd: Sized { + fn add(self, other: Self) -> S1>>, (), true>>>; + } + + fn create_monster_struct(x: T) -> S1>>, (), true>>> { + S1::from(S1::from(S3::from(S2::from(S2::from(S1::from(x)))))) + } + + impl MyAdd for usize { + fn add(self, other: usize) -> S1>>, (), true>>> { + create_monster_struct(self + other) + } + } + + #[derive(Debug)] + struct W(S1>>, (), true>>>); + + impl From for S1>>, (), true>>> { + fn from(x: W) -> Self { + create_monster_struct(x) + } + } + + reuse impl MyAdd for W { + println!("custom_froms {self:?}"); + self.0.a.a.t.t.t.a + } + + pub fn check() { + fn w(x: usize) -> W { + W(create_monster_struct(x)) + } + + assert_eq!(w(1).add(w(2)).a.a.t.t.t.a.0.a.a.t.t.t.a, 3) + } +} + +fn main() { + simple_self::check(); + box_self::check(); + rc_self::check(); + arc_self::check(); + custom_froms::check(); +} diff --git a/tests/ui/delegation/self-mapping-output-from-wrap.run.stdout b/tests/ui/delegation/self-mapping-output-from-wrap.run.stdout new file mode 100644 index 0000000000000..ee96199c54e07 --- /dev/null +++ b/tests/ui/delegation/self-mapping-output-from-wrap.run.stdout @@ -0,0 +1,10 @@ +simple_self W(1) +simple_self W(2) +box_self W(1) +box_self W(2) +rc_self W(1) +rc_self W(2) +arc_self W(1) +arc_self W(2) +custom_froms W(S1 { a: S1 { a: S3 { t: S2 { t: S2 { t: S1 { a: 1 } } }, pd: PhantomData<&[(usize, ()); 123]> } } }) +custom_froms W(S1 { a: S1 { a: S3 { t: S2 { t: S2 { t: S1 { a: 2 } } }, pd: PhantomData<&[(usize, ()); 123]> } } }) From 86b915f4ddcfa4f9bd6f89ec6614a12c9d9eb897 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Wed, 5 Aug 2026 19:16:30 +0300 Subject: [PATCH 24/57] expand: Feature gate AST-based attribute macros on expressions and non-item statements --- compiler/rustc_expand/src/expand.rs | 1 + tests/ui/cfg/cfg-stmt-recovery.rs | 2 +- .../invalid-node-range-issue-129166.rs | 2 +- tests/ui/eii/errors.rs | 2 +- tests/ui/eii/errors.stderr | 13 ++++++++++++- tests/ui/macros/issue-111749.rs | 1 + tests/ui/macros/issue-111749.stderr | 13 ++++++++++++- tests/ui/proc-macro/cfg-eval-fail.rs | 1 + tests/ui/proc-macro/cfg-eval-fail.stderr | 13 ++++++++++++- .../ui/proc-macro/derive-macro-invalid-placement.rs | 2 +- 10 files changed, 43 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 045233c0c4d21..4846b48af8d5e 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -858,6 +858,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)), } } else if let SyntaxExtensionKind::LegacyAttr(expander) = ext { + self.gate_proc_macro_attr_item(span, &item); // `LegacyAttr` is only used for builtin attribute macros, which have their // safety checked by `check_builtin_meta_item`, so we don't need to check // `unsafety` here. diff --git a/tests/ui/cfg/cfg-stmt-recovery.rs b/tests/ui/cfg/cfg-stmt-recovery.rs index f0f9a649165b5..98f79cd8cfc1c 100644 --- a/tests/ui/cfg/cfg-stmt-recovery.rs +++ b/tests/ui/cfg/cfg-stmt-recovery.rs @@ -1,7 +1,7 @@ // Verify that we do not ICE when failing to parse a statement in `cfg_eval`. #![feature(cfg_eval)] -#![feature(stmt_expr_attributes)] +#![feature(stmt_expr_attributes, proc_macro_hygiene)] #[cfg_eval] fn main() { diff --git a/tests/ui/conditional-compilation/invalid-node-range-issue-129166.rs b/tests/ui/conditional-compilation/invalid-node-range-issue-129166.rs index 7c42be3ed4d6e..3f6f902cf3688 100644 --- a/tests/ui/conditional-compilation/invalid-node-range-issue-129166.rs +++ b/tests/ui/conditional-compilation/invalid-node-range-issue-129166.rs @@ -3,7 +3,7 @@ //@ check-pass #![feature(cfg_eval)] -#![feature(stmt_expr_attributes)] +#![feature(stmt_expr_attributes, proc_macro_hygiene)] fn f() -> u32 { #[cfg_eval] #[cfg(not(FALSE))] 0 diff --git a/tests/ui/eii/errors.rs b/tests/ui/eii/errors.rs index bc6c17f463a78..3b28e268662ef 100644 --- a/tests/ui/eii/errors.rs +++ b/tests/ui/eii/errors.rs @@ -8,7 +8,7 @@ #[eii_declaration(bar)] //~ ERROR `#[eii_declaration(...)]` is only valid on macros fn hello() { #[eii_declaration(bar)] //~ ERROR `#[eii_declaration(...)]` is only valid on macros - let x = 3 + 3; + let x = 3 + 3; //~| ERROR custom attributes cannot be applied to statements } #[eii_declaration] //~ ERROR `#[eii_declaration(...)]` expects a list of one or two elements diff --git a/tests/ui/eii/errors.stderr b/tests/ui/eii/errors.stderr index 553ae622cb36f..512cd135de4c3 100644 --- a/tests/ui/eii/errors.stderr +++ b/tests/ui/eii/errors.stderr @@ -4,6 +4,16 @@ error: `#[eii_declaration(...)]` is only valid on macros LL | #[eii_declaration(bar)] | ^^^^^^^^^^^^^^^^^^^^^^^ +error[E0658]: custom attributes cannot be applied to statements + --> $DIR/errors.rs:10:5 + | +LL | #[eii_declaration(bar)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #54727 for more information + = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error: `#[eii_declaration(...)]` is only valid on macros --> $DIR/errors.rs:10:5 | @@ -88,5 +98,6 @@ error: `#[foo]` expected no arguments or a single argument: `#[foo(default)]` LL | #[foo = "default"] | ^^^^^^^^^^^^^^^^^^ -error: aborting due to 14 previous errors +error: aborting due to 15 previous errors +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/macros/issue-111749.rs b/tests/ui/macros/issue-111749.rs index f009a69fe2535..799fee22685ab 100644 --- a/tests/ui/macros/issue-111749.rs +++ b/tests/ui/macros/issue-111749.rs @@ -9,4 +9,5 @@ fn main() { //~^ ERROR the `test` attribute may only be used on a free function //~| ERROR attribute must be of the form `#[test]` //~| WARNING this was previously accepted by the compiler but is being phased out + //~| ERROR custom attributes cannot be applied to expressions } diff --git a/tests/ui/macros/issue-111749.stderr b/tests/ui/macros/issue-111749.stderr index 267f939602b5b..f2773e7029ab5 100644 --- a/tests/ui/macros/issue-111749.stderr +++ b/tests/ui/macros/issue-111749.stderr @@ -1,3 +1,13 @@ +error[E0658]: custom attributes cannot be applied to expressions + --> $DIR/issue-111749.rs:8:17 + | +LL | cbor_map! { #[test(test)] 4i32}; + | ^^^^^^^^^^^^^ + | + = note: see issue #54727 for more information + = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error: the `test` attribute may only be used on a free function --> $DIR/issue-111749.rs:8:17 | @@ -20,8 +30,9 @@ LL | cbor_map! { #[test(test)] 4i32}; = note: for more information, see issue #57571 = note: `#[deny(ill_formed_attribute_input)]` (part of `#[deny(future_incompatible)]`) on by default -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors +For more information about this error, try `rustc --explain E0658`. Future incompatibility report: Future breakage diagnostic: error: attribute must be of the form `#[test]` --> $DIR/issue-111749.rs:8:17 diff --git a/tests/ui/proc-macro/cfg-eval-fail.rs b/tests/ui/proc-macro/cfg-eval-fail.rs index a94dcd2837811..2cde895f2ea44 100644 --- a/tests/ui/proc-macro/cfg-eval-fail.rs +++ b/tests/ui/proc-macro/cfg-eval-fail.rs @@ -4,4 +4,5 @@ fn main() { let _ = #[cfg_eval] #[cfg(false)] 0; //~^ ERROR removing an expression is not supported in this position + //~| ERROR custom attributes cannot be applied to expressions } diff --git a/tests/ui/proc-macro/cfg-eval-fail.stderr b/tests/ui/proc-macro/cfg-eval-fail.stderr index 7f21e4646b1cc..61da346fa69f6 100644 --- a/tests/ui/proc-macro/cfg-eval-fail.stderr +++ b/tests/ui/proc-macro/cfg-eval-fail.stderr @@ -4,5 +4,16 @@ error: removing an expression is not supported in this position LL | let _ = #[cfg_eval] #[cfg(false)] 0; | ^^^^^^^^^^^^^ -error: aborting due to 1 previous error +error[E0658]: custom attributes cannot be applied to expressions + --> $DIR/cfg-eval-fail.rs:5:13 + | +LL | let _ = #[cfg_eval] #[cfg(false)] 0; + | ^^^^^^^^^^^ + | + = note: see issue #54727 for more information + = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 2 previous errors +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/proc-macro/derive-macro-invalid-placement.rs b/tests/ui/proc-macro/derive-macro-invalid-placement.rs index fd24bd7284a92..463e7dc758505 100644 --- a/tests/ui/proc-macro/derive-macro-invalid-placement.rs +++ b/tests/ui/proc-macro/derive-macro-invalid-placement.rs @@ -1,6 +1,6 @@ //! regression test for -#![feature(stmt_expr_attributes)] +#![feature(stmt_expr_attributes, proc_macro_hygiene)] fn foo<#[derive(Debug)] T>() { //~ ERROR expected non-macro attribute, found attribute macro match 0 { From f2dd93228abcf29ab85960457df7a6f828eb3cb9 Mon Sep 17 00:00:00 2001 From: Marius Melzer Date: Mon, 13 Jul 2026 11:39:36 +0200 Subject: [PATCH 25/57] L4Re: Repair build and move to rustc linking Fixes the builds of rustc and library/std for the L4Re target OS. A major change was done in linking binaries: The need for the L4Bender tool was removed and linking parameters are now fully configured in the rustc target config. --- compiler/rustc_codegen_ssa/src/back/linker.rs | 126 ------- compiler/rustc_codegen_ssa/src/diagnostics.rs | 4 - compiler/rustc_target/src/spec/base/l4re.rs | 55 ++- .../targets/x86_64_unknown_l4re_uclibc.rs | 6 +- library/panic_unwind/src/lib.rs | 5 - library/std/src/fs.rs | 1 + library/std/src/fs/tests.rs | 8 +- library/std/src/net/ip_addr.rs | 9 +- library/std/src/net/mod.rs | 2 +- library/std/src/net/socket_addr.rs | 9 +- library/std/src/net/tcp.rs | 1 + library/std/src/net/udp.rs | 1 + library/std/src/os/fd/mod.rs | 1 + library/std/src/os/fd/raw.rs | 2 +- library/std/src/os/l4re/fs.rs | 50 +-- library/std/src/os/l4re/raw.rs | 349 +----------------- library/std/src/os/unix/fs.rs | 1 + library/std/src/os/unix/net/mod.rs | 2 +- library/std/src/process.rs | 1 + library/std/src/process/tests.rs | 98 ++++- library/std/src/random.rs | 2 +- library/std/src/sys/fs/unix.rs | 28 +- library/std/src/sys/io/error/unix.rs | 3 +- library/std/src/sys/net/connection/mod.rs | 2 +- .../std/src/sys/net/connection/socket/mod.rs | 1 + library/std/src/sys/pal/unix/mod.rs | 30 +- library/std/src/sys/personality/mod.rs | 2 +- library/std/src/sys/process/mod.rs | 6 +- library/std/src/sys/process/unix/common.rs | 17 +- .../std/src/sys/process/unix/common/tests.rs | 10 + library/std/src/sys/process/unix/mod.rs | 7 +- .../std/src/sys/process/unix/unix/tests.rs | 5 +- .../std/src/sys/process/unix/unsupported.rs | 2 +- library/std/src/sys/random/mod.rs | 3 +- library/std/src/thread/functions.rs | 9 +- library/std/tests/env.rs | 5 +- library/std/tests/pipe_subprocess.rs | 8 +- library/std/tests/process_spawning.rs | 5 +- library/std/tests/time.rs | 1 + library/unwind/src/lib.rs | 2 +- src/bootstrap/src/utils/helpers.rs | 3 +- 41 files changed, 299 insertions(+), 583 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index 50a3e7fb7a1d1..135faa5817516 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -137,9 +137,6 @@ pub(crate) fn get_linker<'a>( // to the linker args construction. assert!(cmd.get_args().is_empty() || sess.target.cfg_abi == CfgAbi::Uwp); match flavor { - LinkerFlavor::Unix(Cc::No) if sess.target.os == Os::L4Re => { - Box::new(L4Bender::new(cmd, sess)) as Box - } LinkerFlavor::Unix(Cc::No) if sess.target.os == Os::Aix => { Box::new(AixLinker::new(cmd, sess)) as Box } @@ -279,7 +276,6 @@ generate_arg_methods! { MsvcLinker<'_> EmLinker<'_> WasmLd<'_> - L4Bender<'_> AixLinker<'_> LlbcLinker<'_> BpfLinker<'_> @@ -1468,128 +1464,6 @@ impl<'a> WasmLd<'a> { } } -/// Linker shepherd script for L4Re (Fiasco) -struct L4Bender<'a> { - cmd: Command, - sess: &'a Session, - hinted_static: bool, -} - -impl<'a> Linker for L4Bender<'a> { - fn cmd(&mut self) -> &mut Command { - &mut self.cmd - } - - fn set_output_kind( - &mut self, - _output_kind: LinkOutputKind, - _crate_type: CrateType, - _out_filename: &Path, - ) { - } - - fn link_staticlib_by_name(&mut self, name: &str, _verbatim: bool, whole_archive: bool) { - self.hint_static(); - if !whole_archive { - self.link_arg(format!("-PC{name}")); - } else { - self.link_arg("--whole-archive") - .link_or_cc_arg(format!("-l{name}")) - .link_arg("--no-whole-archive"); - } - } - - fn link_staticlib_by_path(&mut self, path: &Path, whole_archive: bool) { - self.hint_static(); - if !whole_archive { - self.link_or_cc_arg(path); - } else { - self.link_arg("--whole-archive").link_or_cc_arg(path).link_arg("--no-whole-archive"); - } - } - - fn full_relro(&mut self) { - self.link_args(&["-z", "relro", "-z", "now"]); - } - - fn partial_relro(&mut self) { - self.link_args(&["-z", "relro"]); - } - - fn no_relro(&mut self) { - self.link_args(&["-z", "norelro"]); - } - - fn gc_sections(&mut self, keep_metadata: bool) { - if !keep_metadata { - self.link_arg("--gc-sections"); - } - } - - fn optimize(&mut self) { - // GNU-style linkers support optimization with -O. GNU ld doesn't - // need a numeric argument, but other linkers do. - if self.sess.opts.optimize == config::OptLevel::More - || self.sess.opts.optimize == config::OptLevel::Aggressive - { - self.link_arg("-O1"); - } - } - - fn pgo_gen(&mut self) {} - - fn debuginfo(&mut self, strip: Strip, _: &[PathBuf]) { - match strip { - Strip::None => {} - Strip::Debuginfo => { - self.link_arg("--strip-debug"); - } - Strip::Symbols => { - self.link_arg("--strip-all"); - } - } - } - - fn no_default_libraries(&mut self) { - self.cc_arg("-nostdlib"); - } - - fn export_symbols(&mut self, _: &Path, _: CrateType, _: &[SymbolExport]) { - // ToDo, not implemented, copy from GCC - self.sess.dcx().emit_warn(diagnostics::L4BenderExportingSymbolsUnimplemented); - } - - fn windows_subsystem(&mut self, subsystem: WindowsSubsystemKind) { - let subsystem = subsystem.as_str(); - self.link_arg(&format!("--subsystem {subsystem}")); - } - - fn reset_per_library_state(&mut self) { - self.hint_static(); // Reset to default before returning the composed command line. - } - - fn linker_plugin_lto(&mut self) {} - - fn control_flow_guard(&mut self) {} - - fn ehcont_guard(&mut self) {} - - fn no_crt_objects(&mut self) {} -} - -impl<'a> L4Bender<'a> { - fn new(cmd: Command, sess: &'a Session) -> L4Bender<'a> { - L4Bender { cmd, sess, hinted_static: false } - } - - fn hint_static(&mut self) { - if !self.hinted_static { - self.link_or_cc_arg("-static"); - self.hinted_static = true; - } - } -} - /// Linker for AIX. struct AixLinker<'a> { cmd: Command, diff --git a/compiler/rustc_codegen_ssa/src/diagnostics.rs b/compiler/rustc_codegen_ssa/src/diagnostics.rs index e6aab553072f2..fbe78b7e78503 100644 --- a/compiler/rustc_codegen_ssa/src/diagnostics.rs +++ b/compiler/rustc_codegen_ssa/src/diagnostics.rs @@ -97,10 +97,6 @@ pub(crate) struct Ld64UnimplementedModifier; #[diag("`as-needed` modifier not supported for current linker")] pub(crate) struct LinkerUnsupportedModifier; -#[derive(Diagnostic)] -#[diag("exporting symbols not implemented yet for L4Bender")] -pub(crate) struct L4BenderExportingSymbolsUnimplemented; - #[derive(Diagnostic)] #[diag("error enumerating natvis directory: {$error}")] pub(crate) struct NoNatvisDirectory { diff --git a/compiler/rustc_target/src/spec/base/l4re.rs b/compiler/rustc_target/src/spec/base/l4re.rs index 8722c8a71e23a..cc67bd6d3a487 100644 --- a/compiler/rustc_target/src/spec/base/l4re.rs +++ b/compiler/rustc_target/src/spec/base/l4re.rs @@ -1,14 +1,59 @@ -use crate::spec::{Cc, Env, LinkerFlavor, Os, PanicStrategy, RelocModel, TargetOptions, cvs}; +use crate::spec::{ + Cc, Env, LinkOutputKind, LinkSelfContainedComponents, LinkSelfContainedDefault, LinkerFlavor, + Os, PanicStrategy, TargetOptions, add_link_args, crt_objects, cvs, +}; pub(crate) fn opts() -> TargetOptions { + // add ld- and cc-style args + macro_rules! prepare_args { + ($($val:expr),+) => {{ + let ld_args = &[$($val),+]; + let cc_args = &[$(concat!("-Wl,", $val)),+]; + + let mut ret = TargetOptions::link_args(LinkerFlavor::Unix(Cc::No), ld_args); + add_link_args(&mut ret, LinkerFlavor::Unix(Cc::Yes), cc_args); + ret + }}; + } + + let pre_link_args = prepare_args!("-nostdlib", "-dynamic-linker=rom/libld-l4.so"); + + let late_link_args = prepare_args!("-lc", "-lgcc_eh"); + + let pre_link_objects_self_contained = crt_objects::new(&[ + (LinkOutputKind::StaticNoPicExe, &["crt1.o", "crti.o", "crtbeginT.o"]), + (LinkOutputKind::StaticPicExe, &["crt1.p.o", "crti.o", "crtbegin.o"]), + (LinkOutputKind::DynamicNoPicExe, &["crt1.o", "crti.o", "crtbegin.o"]), + (LinkOutputKind::DynamicPicExe, &["crt1.s.o", "crti.o", "crtbeginS.o"]), + (LinkOutputKind::DynamicDylib, &["crti.s.o", "crtbeginS.o"]), + (LinkOutputKind::StaticDylib, &["crti.s.o", "crtbeginS.o"]), + ]); + + let post_link_objects_self_contained = crt_objects::new(&[ + (LinkOutputKind::StaticNoPicExe, &["crtendT.o", "crtn.o"]), + (LinkOutputKind::StaticPicExe, &["crtend.o", "crtn.o"]), + (LinkOutputKind::DynamicNoPicExe, &["crtend.o", "crtn.o"]), + (LinkOutputKind::DynamicPicExe, &["crtendS.o", "crtn.o"]), + (LinkOutputKind::DynamicDylib, &["crtendS.o", "crtn.s.o"]), + (LinkOutputKind::StaticDylib, &["crtendS.o", "crtn.s.o"]), + ]); + TargetOptions { os: Os::L4Re, env: Env::Uclibc, - linker_flavor: LinkerFlavor::Unix(Cc::No), - panic_strategy: PanicStrategy::Abort, - linker: Some("l4-bender".into()), families: cvs!["unix"], - relocation_model: RelocModel::Static, + panic_strategy: PanicStrategy::Abort, + linker_flavor: LinkerFlavor::Unix(Cc::No), + dynamic_linking: true, + position_independent_executables: true, + has_thread_local: true, + pre_link_args, + late_link_args, + pre_link_objects_self_contained, + post_link_objects_self_contained, + link_self_contained: LinkSelfContainedDefault::WithComponents( + LinkSelfContainedComponents::LIBC | LinkSelfContainedComponents::CRT_OBJECTS, + ), ..Default::default() } } diff --git a/compiler/rustc_target/src/spec/targets/x86_64_unknown_l4re_uclibc.rs b/compiler/rustc_target/src/spec/targets/x86_64_unknown_l4re_uclibc.rs index 5ab6b094dfa06..7030a98305a0b 100644 --- a/compiler/rustc_target/src/spec/targets/x86_64_unknown_l4re_uclibc.rs +++ b/compiler/rustc_target/src/spec/targets/x86_64_unknown_l4re_uclibc.rs @@ -1,11 +1,13 @@ -use crate::spec::{Arch, PanicStrategy, Target, TargetMetadata, base}; +use crate::spec::{Arch, Cc, LinkerFlavor, Target, TargetMetadata, base}; pub(crate) fn target() -> Target { let mut base = base::l4re::opts(); base.cpu = "x86-64".into(); base.plt_by_default = false; base.max_atomic_width = Some(64); - base.panic_strategy = PanicStrategy::Abort; + let extra_link_args = &["-zmax-page-size=0x1000", "-zcommon-page-size=0x1000"]; + base.add_pre_link_args(LinkerFlavor::Unix(Cc::Yes), extra_link_args); + base.add_pre_link_args(LinkerFlavor::Unix(Cc::No), extra_link_args); Target { llvm_target: "x86_64-unknown-l4re-gnu".into(), diff --git a/library/panic_unwind/src/lib.rs b/library/panic_unwind/src/lib.rs index 9d204a150dd45..1644a2495d97e 100644 --- a/library/panic_unwind/src/lib.rs +++ b/library/panic_unwind/src/lib.rs @@ -36,11 +36,6 @@ cfg_select! { #[path = "hermit.rs"] mod imp; } - target_os = "l4re" => { - // L4Re is unix family but does not yet support unwinding. - #[path = "dummy.rs"] - mod imp; - } any( all(target_family = "windows", target_env = "gnu"), target_os = "psp", diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 4c5cd0e0c9e6a..3b8499758d90d 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -37,6 +37,7 @@ target_env = "sgx", target_os = "xous", target_os = "trusty", + target_os = "l4re", )) ))] mod tests; diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 1b069f2e77f6b..2a656e2f8c196 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -962,6 +962,10 @@ fn recursive_mkdir_slash() { } #[test] +#[cfg_attr( + target_os = "l4re", + ignore = "Path '.' in the file system root can not be resolved in L4Re" +)] fn recursive_mkdir_dot() { check!(fs::create_dir_all(Path::new("."))); } @@ -2117,6 +2121,7 @@ fn rename_directory() { } #[test] +#[cfg_attr(target_os = "l4re", ignore = "futimens")] fn test_file_times() { #[cfg(target_vendor = "apple")] use crate::os::darwin::fs::FileTimesExt; @@ -2145,7 +2150,8 @@ fn test_file_times() { target_os = "android", target_os = "redox", target_os = "espidf", - target_os = "horizon" + target_os = "horizon", + target_os = "l4re", )) ) )))] diff --git a/library/std/src/net/ip_addr.rs b/library/std/src/net/ip_addr.rs index 7262899b3bbbe..6bd78de910fec 100644 --- a/library/std/src/net/ip_addr.rs +++ b/library/std/src/net/ip_addr.rs @@ -1,5 +1,12 @@ // Tests for this module -#[cfg(all(test, not(any(target_os = "emscripten", all(target_os = "wasi", target_env = "p1")))))] +#[cfg(all( + test, + not(any( + target_os = "emscripten", + all(target_os = "wasi", target_env = "p1"), + target_os = "l4re" + )) +))] mod tests; #[stable(feature = "ip_addr", since = "1.7.0")] diff --git a/library/std/src/net/mod.rs b/library/std/src/net/mod.rs index 2a8b0f8ca9aad..1b1096925dd4a 100644 --- a/library/std/src/net/mod.rs +++ b/library/std/src/net/mod.rs @@ -42,7 +42,7 @@ mod hostname; mod ip_addr; mod socket_addr; mod tcp; -#[cfg(test)] +#[cfg(all(test, not(target_os = "l4re")))] pub(crate) mod tests; mod udp; diff --git a/library/std/src/net/socket_addr.rs b/library/std/src/net/socket_addr.rs index cae14e34e73e7..2dab8c26f1f6b 100644 --- a/library/std/src/net/socket_addr.rs +++ b/library/std/src/net/socket_addr.rs @@ -1,5 +1,12 @@ // Tests for this module -#[cfg(all(test, not(any(target_os = "emscripten", all(target_os = "wasi", target_env = "p1")))))] +#[cfg(all( + test, + not(any( + target_os = "emscripten", + all(target_os = "wasi", target_env = "p1"), + target_os = "l4re" + )) +))] mod tests; #[stable(feature = "rust1", since = "1.0.0")] diff --git a/library/std/src/net/tcp.rs b/library/std/src/net/tcp.rs index d9090320bd5a6..4ba4c4e8caa4c 100644 --- a/library/std/src/net/tcp.rs +++ b/library/std/src/net/tcp.rs @@ -7,6 +7,7 @@ all(target_os = "wasi", target_env = "p1"), target_os = "xous", target_os = "trusty", + target_os = "l4re", )) ))] mod tests; diff --git a/library/std/src/net/udp.rs b/library/std/src/net/udp.rs index cd925b9bdfdf8..4aa77fc9c1fe9 100644 --- a/library/std/src/net/udp.rs +++ b/library/std/src/net/udp.rs @@ -6,6 +6,7 @@ target_env = "sgx", target_os = "xous", target_os = "trusty", + target_os = "l4re", )) ))] mod tests; diff --git a/library/std/src/os/fd/mod.rs b/library/std/src/os/fd/mod.rs index 473d7ae3e2ae6..735f1cf8925fb 100644 --- a/library/std/src/os/fd/mod.rs +++ b/library/std/src/os/fd/mod.rs @@ -20,6 +20,7 @@ mod net; mod stdio; #[cfg(test)] +#[cfg(not(target_os = "l4re"))] mod tests; // Export the types and traits for the public API. diff --git a/library/std/src/os/fd/raw.rs b/library/std/src/os/fd/raw.rs index 0d96958b6cca1..a0c96e2836fc5 100644 --- a/library/std/src/os/fd/raw.rs +++ b/library/std/src/os/fd/raw.rs @@ -16,7 +16,7 @@ use crate::io; use crate::os::hermit::io::OwnedFd; #[cfg(all(not(target_os = "hermit"), not(target_os = "motor")))] use crate::os::raw; -#[cfg(all(doc, not(any(target_arch = "wasm32", target_env = "sgx"))))] +#[cfg(all(doc, not(any(target_arch = "wasm32", target_env = "sgx", target_os = "l4re"))))] use crate::os::unix::io::AsFd; #[cfg(unix)] use crate::os::unix::io::OwnedFd; diff --git a/library/std/src/os/l4re/fs.rs b/library/std/src/os/l4re/fs.rs index 491e04a4d25cf..2dc899bcb5a6e 100644 --- a/library/std/src/os/l4re/fs.rs +++ b/library/std/src/os/l4re/fs.rs @@ -21,7 +21,7 @@ pub trait MetadataExt { /// Unix platforms. The `os::unix::fs::MetadataExt` trait contains the /// cross-Unix abstractions contained within the raw stat. /// - /// [`stat`]: struct@crate::os::linux::raw::stat + /// [`stat`]: struct@crate::os::l4re::raw::stat /// /// # Examples /// @@ -29,7 +29,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -50,7 +50,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -68,7 +68,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -86,7 +86,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -104,7 +104,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -122,7 +122,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -140,7 +140,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -158,7 +158,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -179,7 +179,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -197,7 +197,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -217,7 +217,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -235,7 +235,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -255,7 +255,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -273,7 +273,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -293,7 +293,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -311,7 +311,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -329,7 +329,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -345,7 +345,7 @@ pub trait MetadataExt { impl MetadataExt for Metadata { #[allow(deprecated)] fn as_raw_stat(&self) -> &raw::stat { - unsafe { &*(self.as_inner().as_inner() as *const libc::stat64 as *const raw::stat) } + unsafe { &*(self.as_inner().as_inner() as *const _ as *const raw::stat) } } fn st_dev(&self) -> u64 { self.as_inner().as_inner().st_dev as u64 @@ -372,22 +372,22 @@ impl MetadataExt for Metadata { self.as_inner().as_inner().st_size as u64 } fn st_atime(&self) -> i64 { - self.as_inner().as_inner().st_atime as i64 + self.as_inner().as_inner().st_atim.tv_sec as i64 } fn st_atime_nsec(&self) -> i64 { - self.as_inner().as_inner().st_atime_nsec as i64 + self.as_inner().as_inner().st_atim.tv_nsec as i64 } fn st_mtime(&self) -> i64 { - self.as_inner().as_inner().st_mtime as i64 + self.as_inner().as_inner().st_mtim.tv_sec as i64 } fn st_mtime_nsec(&self) -> i64 { - self.as_inner().as_inner().st_mtime_nsec as i64 + self.as_inner().as_inner().st_mtim.tv_nsec as i64 } fn st_ctime(&self) -> i64 { - self.as_inner().as_inner().st_ctime as i64 + self.as_inner().as_inner().st_ctim.tv_sec as i64 } fn st_ctime_nsec(&self) -> i64 { - self.as_inner().as_inner().st_ctime_nsec as i64 + self.as_inner().as_inner().st_ctim.tv_nsec as i64 } fn st_blksize(&self) -> u64 { self.as_inner().as_inner().st_blksize as u64 diff --git a/library/std/src/os/l4re/raw.rs b/library/std/src/os/l4re/raw.rs index 8fb6e99ecfa1e..f41fff015cab6 100644 --- a/library/std/src/os/l4re/raw.rs +++ b/library/std/src/os/l4re/raw.rs @@ -10,355 +10,14 @@ )] #![allow(deprecated)] -use crate::os::raw::c_ulong; - #[stable(feature = "raw_ext", since = "1.1.0")] -pub type dev_t = u64; +pub type dev_t = libc::dev_t; #[stable(feature = "raw_ext", since = "1.1.0")] -pub type mode_t = u32; +pub type mode_t = libc::mode_t; #[stable(feature = "pthread_t", since = "1.8.0")] -pub type pthread_t = c_ulong; +pub type pthread_t = libc::pthread_t; #[doc(inline)] #[stable(feature = "raw_ext", since = "1.1.0")] -pub use self::arch::{blkcnt_t, blksize_t, ino_t, nlink_t, off_t, stat, time_t}; - -#[cfg(any( - target_arch = "x86", - target_arch = "m68k", - target_arch = "csky", - target_arch = "powerpc", - target_arch = "sparc", - target_arch = "arm", - target_arch = "wasm32" -))] -mod arch { - use crate::os::raw::{c_long, c_short, c_uint}; - - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad1: c_short, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __st_ino: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad2: c_uint, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - } -} - -#[cfg(target_arch = "mips")] -mod arch { - use crate::os::raw::{c_long, c_ulong}; - - #[cfg(target_env = "musl")] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = i64; - #[cfg(not(target_env = "musl"))] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = u64; - #[cfg(target_env = "musl")] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[cfg(not(target_env = "musl"))] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u64; - #[cfg(target_env = "musl")] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; - #[cfg(not(target_env = "musl"))] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: c_ulong, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_pad1: [c_long; 3], - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: c_ulong, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_pad2: [c_long; 2], - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_pad5: [c_long; 14], - } -} - -#[cfg(target_arch = "hexagon")] -mod arch { - use crate::os::raw::{c_int, c_long, c_uint}; - - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = i64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = c_long; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = c_uint; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = i64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad1: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad2: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad3: [c_int; 2], - } -} - -#[cfg(any( - target_arch = "mips64", - target_arch = "s390x", - target_arch = "sparc64", - target_arch = "riscv64", - target_arch = "riscv32" -))] -mod arch { - pub use libc::{blkcnt_t, blksize_t, ino_t, nlink_t, off_t, stat, time_t}; -} - -#[cfg(target_arch = "aarch64")] -mod arch { - use crate::os::raw::{c_int, c_long}; - - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = i64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = i32; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u32; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = i64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = c_long; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad1: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad2: c_int, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: time_t, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: time_t, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: time_t, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __unused: [c_int; 2], - } -} - -#[cfg(any(target_arch = "x86_64", target_arch = "powerpc64"))] -mod arch { - use crate::os::raw::{c_int, c_long}; - - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad0: c_int, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __unused: [c_long; 3], - } -} +pub use libc::{blkcnt_t, blksize_t, ino_t, nlink_t, off_t, stat, time_t}; diff --git a/library/std/src/os/unix/fs.rs b/library/std/src/os/unix/fs.rs index 90ad137dac178..aa604aabaffce 100644 --- a/library/std/src/os/unix/fs.rs +++ b/library/std/src/os/unix/fs.rs @@ -18,6 +18,7 @@ use crate::sys::{AsInner, AsInnerMut, FromInner}; use crate::{io, sys}; // Tests for this module +#[cfg(not(target_os = "l4re"))] #[cfg(test)] mod tests; diff --git a/library/std/src/os/unix/net/mod.rs b/library/std/src/os/unix/net/mod.rs index 137088dd832f7..92d2696a5ef43 100644 --- a/library/std/src/os/unix/net/mod.rs +++ b/library/std/src/os/unix/net/mod.rs @@ -10,7 +10,7 @@ mod ancillary; mod datagram; mod listener; mod stream; -#[cfg(all(test, not(target_os = "emscripten")))] +#[cfg(all(test, not(any(target_os = "emscripten", target_os = "l4re"))))] mod tests; #[cfg(any( target_os = "android", diff --git a/library/std/src/process.rs b/library/std/src/process.rs index c5ffbbc666e43..a398363cf4bf9 100644 --- a/library/std/src/process.rs +++ b/library/std/src/process.rs @@ -157,6 +157,7 @@ target_os = "xous", target_os = "trusty", target_os = "hermit", + target_os = "l4re", )) ))] mod tests; diff --git a/library/std/src/process/tests.rs b/library/std/src/process/tests.rs index 68c62a861075f..9fe14b2e468a5 100644 --- a/library/std/src/process/tests.rs +++ b/library/std/src/process/tests.rs @@ -28,7 +28,11 @@ fn shell_cmd() -> Command { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn smoke() { @@ -53,7 +57,11 @@ fn smoke_failure() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn exit_reported_right() { @@ -71,7 +79,11 @@ fn exit_reported_right() { #[test] #[cfg(unix)] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn signal_reported_right() { @@ -98,7 +110,11 @@ pub fn run_output(mut cmd: Command) -> String { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn stdout_works() { @@ -116,7 +132,11 @@ fn stdout_works() { #[test] #[cfg_attr(windows, ignore)] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn set_current_dir_works() { @@ -142,7 +162,11 @@ fn set_current_dir_works() { #[test] #[cfg_attr(windows, ignore)] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn stdin_works() { @@ -163,7 +187,11 @@ fn stdin_works() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn child_stdout_read_buf() { @@ -197,7 +225,11 @@ fn child_stdout_read_buf() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_process_status() { @@ -217,6 +249,7 @@ fn test_process_status() { } #[test] +#[cfg_attr(any(target_os = "l4re"), ignore = "no fork/exec available")] fn test_process_output_fail_to_start() { match Command::new("/no-binary-by-this-name-should-exist").output() { Err(e) => assert_eq!(e.kind(), ErrorKind::NotFound), @@ -226,7 +259,11 @@ fn test_process_output_fail_to_start() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_process_output_output() { @@ -244,7 +281,11 @@ fn test_process_output_output() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_process_output_error() { @@ -262,7 +303,11 @@ fn test_process_output_error() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_finish_once() { @@ -276,7 +321,11 @@ fn test_finish_once() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_finish_twice() { @@ -291,7 +340,11 @@ fn test_finish_twice() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_wait_with_output_once() { @@ -329,7 +382,11 @@ pub fn env_cmd() -> Command { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_override_env() { @@ -355,7 +412,11 @@ fn test_override_env() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_add_to_env() { @@ -370,7 +431,11 @@ fn test_add_to_env() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_capture_env_at_spawn() { @@ -654,6 +719,7 @@ fn run_canonical_bat_script() { } #[test] +#[cfg_attr(target_os = "l4re", ignore = "no shell available")] fn terminate_exited_process() { let mut cmd = if cfg!(target_os = "android") { let mut p = shell_cmd(); diff --git a/library/std/src/random.rs b/library/std/src/random.rs index ef561d1ed0c60..853756fcd32b6 100644 --- a/library/std/src/random.rs +++ b/library/std/src/random.rs @@ -103,7 +103,7 @@ use crate::sys::random as sys; /// Vita | `arc4random_buf` /// Hermit | `read_entropy` /// Horizon, Cygwin | `getrandom` -/// AIX, Hurd, L4Re, QNX | `/dev/urandom` +/// AIX, Hurd, QNX | `/dev/urandom` /// Redox | `/scheme/rand` /// RTEMS | [`arc4random_buf`](https://docs.rtems.org/branches/main/bsp-howto/getentropy.html) /// SGX | [`rdrand`](https://en.wikipedia.org/wiki/RDRAND) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 3caa41e16845d..d34621083406a 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -29,19 +29,20 @@ use libc::{ }; #[cfg(not(any( all(target_os = "linux", not(target_env = "musl")), - target_os = "l4re", target_os = "android", target_os = "hurd", + target_os = "l4re", )))] use libc::{ dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, lstat as lstat64, off_t as off64_t, open as open64, stat as stat64, }; -#[cfg(any( - all(target_os = "linux", not(target_env = "musl")), - target_os = "l4re", - target_os = "hurd" -))] +#[cfg(target_os = "l4re")] +use libc::{ + dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, lstat as lstat64, + off_t as off64_t, open as open64, stat as stat64, +}; +#[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))] use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64}; use crate::ffi::{CStr, OsStr, OsString}; @@ -272,6 +273,7 @@ cfg_select! { target_os = "nto", target_os = "qnx", target_os = "vxworks", + target_os = "l4re", ) => { pub use crate::sys::fs::common::Dir; } @@ -560,7 +562,8 @@ impl FileAttr { target_os = "nto", target_os = "qnx", target_os = "aix", - target_os = "wasi" + target_os = "wasi", + target_os = "l4re" )))] impl FileAttr { #[cfg(not(any( @@ -686,7 +689,7 @@ impl FileAttr { } } -#[cfg(any(target_os = "nto", target_os = "qnx", target_os = "wasi"))] +#[cfg(any(target_os = "nto", target_os = "qnx", target_os = "wasi", target_os = "l4re"))] impl FileAttr { pub fn modified(&self) -> io::Result { SystemTime::new(self.stat.st_mtim.tv_sec, self.stat.st_mtim.tv_nsec.into()) @@ -1066,6 +1069,7 @@ impl DirEntry { target_os = "nto", target_os = "qnx", target_os = "vita", + target_os = "l4re", ))] pub fn file_type(&self) -> io::Result { self.metadata().map(|m| m.file_type()) @@ -1080,6 +1084,7 @@ impl DirEntry { target_os = "nto", target_os = "qnx", target_os = "vita", + target_os = "l4re", )))] pub fn file_type(&self) -> io::Result { match self.entry.d_type { @@ -1289,6 +1294,7 @@ impl File { target_os = "nto", target_os = "qnx", target_os = "hurd", + target_os = "l4re", ))] unsafe fn os_datasync(fd: c_int) -> c_int { libc::fdatasync(fd) @@ -1304,6 +1310,7 @@ impl File { target_os = "nto", target_os = "qnx", target_os = "hurd", + target_os = "l4re", target_vendor = "apple", )))] unsafe fn os_datasync(fd: c_int) -> c_int { @@ -1550,7 +1557,7 @@ impl File { pub fn set_times(&self, times: FileTimes) -> io::Result<()> { cfg_select! { - any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx") => { + any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx", target_os = "l4re") => { // Redox doesn't appear to support `UTIME_OMIT`. // ESP-IDF and HorizonOS do not support `futimens` at all and the behavior for those OS is therefore // the same as for Redox. @@ -1940,6 +1947,7 @@ pub fn link(original: &CStr, link: &CStr) -> io::Result<()> { // Other misc platforms target_os = "horizon", target_os = "vita", + target_os = "l4re", target_env = "nto70", ) => { cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?; @@ -2308,6 +2316,7 @@ pub use remove_dir_impl::remove_dir_all; target_os = "nto", target_os = "qnx", target_os = "vxworks", + target_os = "l4re", miri ))] mod remove_dir_impl { @@ -2323,6 +2332,7 @@ mod remove_dir_impl { target_os = "nto", target_os = "qnx", target_os = "vxworks", + target_os = "l4re", miri )))] mod remove_dir_impl { diff --git a/library/std/src/sys/io/error/unix.rs b/library/std/src/sys/io/error/unix.rs index 89647ff27ca8e..5c51c5705a7aa 100644 --- a/library/std/src/sys/io/error/unix.rs +++ b/library/std/src/sys/io/error/unix.rs @@ -201,7 +201,8 @@ pub fn error_string(errno: i32) -> String { target_os = "linux", target_os = "hurd", target_env = "newlib", - target_os = "cygwin" + target_os = "cygwin", + target_env = "uclibc", ), not(target_env = "ohos") ), diff --git a/library/std/src/sys/net/connection/mod.rs b/library/std/src/sys/net/connection/mod.rs index 84b53fd375c93..49a0f47c959d2 100644 --- a/library/std/src/sys/net/connection/mod.rs +++ b/library/std/src/sys/net/connection/mod.rs @@ -1,6 +1,6 @@ cfg_select! { any( - all(target_family = "unix", not(target_os = "l4re")), + target_family = "unix", target_os = "windows", target_os = "hermit", all(target_os = "wasi", any(target_env = "p2", target_env = "p3")), diff --git a/library/std/src/sys/net/connection/socket/mod.rs b/library/std/src/sys/net/connection/socket/mod.rs index 66aa2a804db22..e3d06bbf65b2d 100644 --- a/library/std/src/sys/net/connection/socket/mod.rs +++ b/library/std/src/sys/net/connection/socket/mod.rs @@ -1,4 +1,5 @@ #[cfg(test)] +#[cfg(not(target_os = "l4re"))] mod tests; use crate::ffi::{c_int, c_void}; diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 8fca169d93119..f58c7f5cb95bc 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -145,6 +145,7 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { target_os = "horizon", target_os = "vxworks", target_os = "vita", + target_os = "l4re", // Unikraft's `signal` implementation is currently broken: // https://github.com/unikraft/lib-musl/issues/57 target_vendor = "unikraft", @@ -363,15 +364,24 @@ cfg_select! { _ => {} } -#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "nuttx"))] -pub mod unsupported { - use crate::io; - - pub fn unsupported() -> io::Result { - Err(unsupported_err()) - } +#[cfg(any( + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nuttx", + target_os = "l4re", +))] +pub fn unsupported() -> crate::io::Result { + Err(unsupported_err()) +} - pub fn unsupported_err() -> io::Error { - io::Error::UNSUPPORTED_PLATFORM - } +#[cfg(any( + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nuttx", + target_os = "l4re", +))] +pub fn unsupported_err() -> crate::io::Error { + io::Error::UNSUPPORTED_PLATFORM } diff --git a/library/std/src/sys/personality/mod.rs b/library/std/src/sys/personality/mod.rs index 3b363aa2d024c..daa53703994b0 100644 --- a/library/std/src/sys/personality/mod.rs +++ b/library/std/src/sys/personality/mod.rs @@ -30,7 +30,7 @@ cfg_select! { target_os = "psp", target_os = "xous", target_os = "solid_asp3", - all(target_family = "unix", not(target_os = "espidf"), not(target_os = "l4re"), not(target_os = "nuttx")), + all(target_family = "unix", not(target_os = "espidf"), not(target_os = "nuttx")), all(target_vendor = "fortanix", target_env = "sgx"), ) => { mod gcc; diff --git a/library/std/src/sys/process/mod.rs b/library/std/src/sys/process/mod.rs index ee61175a278b0..f46870e0c4042 100644 --- a/library/std/src/sys/process/mod.rs +++ b/library/std/src/sys/process/mod.rs @@ -45,7 +45,8 @@ pub use imp::{ target_os = "espidf", target_os = "horizon", target_os = "vita", - target_os = "nuttx" + target_os = "nuttx", + target_os = "l4re" )) ), target_os = "windows", @@ -83,7 +84,8 @@ pub fn output(cmd: &mut Command) -> crate::io::Result<(ExitStatus, Vec, Vec< target_os = "espidf", target_os = "horizon", target_os = "vita", - target_os = "nuttx" + target_os = "nuttx", + target_os = "l4re" )) ), target_os = "windows", diff --git a/library/std/src/sys/process/unix/common.rs b/library/std/src/sys/process/unix/common.rs index 8215b196127ac..2e32770e90e77 100644 --- a/library/std/src/sys/process/unix/common.rs +++ b/library/std/src/sys/process/unix/common.rs @@ -12,7 +12,7 @@ use crate::path::Path; use crate::process::StdioPipes; use crate::sys::fd::FileDesc; use crate::sys::fs::File; -#[cfg(not(target_os = "fuchsia"))] +#[cfg(not(any(target_os = "fuchsia", target_os = "l4re")))] use crate::sys::fs::OpenOptions; use crate::sys::pipe::pipe; use crate::sys::process::env::{CommandEnv, CommandEnvs, CommandResolvedEnvs}; @@ -24,6 +24,9 @@ mod cstring_array; cfg_select! { target_os = "fuchsia" => { // fuchsia doesn't have /dev/null + }, + target_os = "l4re" => { + // l4re doesn't have /dev/null } target_os = "vxworks" => { const DEV_NULL: &CStr = c"/null"; @@ -119,9 +122,9 @@ pub enum ChildStdio { Explicit(c_int), Owned(FileDesc), - // On Fuchsia, null stdio is the default, so we simply don't specify - // any actions at the time of spawning. - #[cfg(target_os = "fuchsia")] + // On Fuchsia and L4Re, null stdio is the default, so we simply don't + // specify any actions at the time of spawning. + #[cfg(any(target_os = "fuchsia", target_os = "l4re"))] Null, } @@ -427,7 +430,7 @@ impl Stdio { Ok((ChildStdio::Owned(theirs), Some(ours))) } - #[cfg(not(target_os = "fuchsia"))] + #[cfg(not(any(target_os = "fuchsia", target_os = "l4re")))] Stdio::Null => { let mut opts = OpenOptions::new(); opts.read(readable); @@ -436,7 +439,7 @@ impl Stdio { Ok((ChildStdio::Owned(fd.into_inner()), None)) } - #[cfg(target_os = "fuchsia")] + #[cfg(any(target_os = "fuchsia", target_os = "l4re"))] Stdio::Null => Ok((ChildStdio::Null, None)), } } @@ -483,7 +486,7 @@ impl ChildStdio { ChildStdio::Explicit(fd) => Some(fd), ChildStdio::Owned(ref fd) => Some(fd.as_raw_fd()), - #[cfg(target_os = "fuchsia")] + #[cfg(any(target_os = "fuchsia", target_os = "l4re"))] ChildStdio::Null => None, } } diff --git a/library/std/src/sys/process/unix/common/tests.rs b/library/std/src/sys/process/unix/common/tests.rs index bc1d158b74861..eacb4d2d43122 100644 --- a/library/std/src/sys/process/unix/common/tests.rs +++ b/library/std/src/sys/process/unix/common/tests.rs @@ -19,6 +19,8 @@ macro_rules! t { // newly spawned process may just be raced in the macOS, so to prevent this // test from being flaky we ignore it on macOS. target_os = "macos", + // cat not available + target_os = "l4re", // When run under our current QEMU emulation test suite this test fails, // although the reason isn't very clear as to why. For now this test is // ignored there. @@ -84,6 +86,8 @@ fn test_process_mask() { any( // See test_process_mask target_os = "macos", + // cat not available + target_os = "l4re", target_arch = "arm", target_arch = "aarch64", target_arch = "riscv64", @@ -116,6 +120,8 @@ fn test_process_group_posix_spawn() { any( // See test_process_mask target_os = "macos", + // cat not available + target_os = "l4re", target_arch = "arm", target_arch = "aarch64", target_arch = "riscv64", @@ -154,6 +160,8 @@ fn test_process_group_no_posix_spawn() { any( // See test_process_mask target_os = "macos", + // cat not available + target_os = "l4re", target_arch = "arm", target_arch = "aarch64", target_arch = "riscv64", @@ -192,6 +200,8 @@ fn test_setsid_posix_spawn() { any( // See test_process_mask target_os = "macos", + // cat not available + target_os = "l4re", target_arch = "arm", target_arch = "aarch64", target_arch = "riscv64", diff --git a/library/std/src/sys/process/unix/mod.rs b/library/std/src/sys/process/unix/mod.rs index 837761431e990..47baf5a1e92ed 100644 --- a/library/std/src/sys/process/unix/mod.rs +++ b/library/std/src/sys/process/unix/mod.rs @@ -1,4 +1,7 @@ -#[cfg_attr(any(target_os = "espidf", target_os = "horizon", target_os = "nuttx"), allow(unused))] +#[cfg_attr( + any(target_os = "espidf", target_os = "horizon", target_os = "nuttx", target_os = "l4re"), + allow(unused) +)] mod common; cfg_select! { @@ -10,7 +13,7 @@ cfg_select! { mod vxworks; use vxworks as imp; } - any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "nuttx") => { + any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "nuttx", target_os = "l4re") => { mod unsupported; use unsupported as imp; pub use unsupported::output; diff --git a/library/std/src/sys/process/unix/unix/tests.rs b/library/std/src/sys/process/unix/unix/tests.rs index 663ba61f966c9..9a029f16a3a20 100644 --- a/library/std/src/sys/process/unix/unix/tests.rs +++ b/library/std/src/sys/process/unix/unix/tests.rs @@ -51,7 +51,10 @@ fn exitstatus_display_tests() { #[test] #[cfg_attr(target_os = "emscripten", ignore)] -#[cfg_attr(any(target_os = "tvos", target_os = "watchos"), ignore = "fork is prohibited")] +#[cfg_attr( + any(target_os = "tvos", target_os = "watchos", target_os = "l4re"), + ignore = "fork is prohibited" +)] fn test_command_fork_no_unwind() { let got = catch_unwind(|| { let mut c = Command::new("echo"); diff --git a/library/std/src/sys/process/unix/unsupported.rs b/library/std/src/sys/process/unix/unsupported.rs index 17421d1e2e35d..2235ec1f1c3b6 100644 --- a/library/std/src/sys/process/unix/unsupported.rs +++ b/library/std/src/sys/process/unix/unsupported.rs @@ -4,7 +4,7 @@ use super::common::*; use crate::io; use crate::num::NonZero; use crate::process::StdioPipes; -use crate::sys::pal::unsupported::*; +use crate::sys::pal::{unsupported, unsupported_err}; //////////////////////////////////////////////////////////////////////////////// // Command diff --git a/library/std/src/sys/random/mod.rs b/library/std/src/sys/random/mod.rs index e5a66dc463c6b..5b0d19cc63eca 100644 --- a/library/std/src/sys/random/mod.rs +++ b/library/std/src/sys/random/mod.rs @@ -52,7 +52,6 @@ cfg_select! { any( target_os = "aix", target_os = "hurd", - target_os = "l4re", target_os = "nto", target_os = "qnx", ) => { @@ -107,6 +106,7 @@ cfg_select! { all(target_family = "wasm", target_os = "unknown"), target_os = "xous", target_os = "vexos", + target_os = "l4re", ) => { // FIXME: finally remove std support for wasm32-unknown-unknown // FIXME: add random data generation to xous @@ -123,6 +123,7 @@ cfg_select! { all(target_os = "wasi", not(target_env = "p1")), target_os = "xous", target_os = "vexos", + target_os = "l4re", )))] pub fn hashmap_random_keys() -> (u64, u64) { let mut buf = [0; 16]; diff --git a/library/std/src/thread/functions.rs b/library/std/src/thread/functions.rs index 21e7a2b2ed087..355a00c2a95ad 100644 --- a/library/std/src/thread/functions.rs +++ b/library/std/src/thread/functions.rs @@ -681,13 +681,10 @@ pub fn park_timeout(dur: Duration) { /// # Examples /// /// ``` -/// # #![allow(dead_code)] -/// use std::{io, thread}; +/// use std::thread; /// -/// fn main() -> io::Result<()> { -/// let count = thread::available_parallelism()?.get(); -/// assert!(count >= 1_usize); -/// Ok(()) +/// if let Ok(count) = thread::available_parallelism() { +/// assert!(count.get() >= 1_usize); /// } /// ``` #[doc(alias = "available_concurrency")] // Alias for a previous name we gave this API on unstable. diff --git a/library/std/tests/env.rs b/library/std/tests/env.rs index 9d624d5592ce7..758d0a069a831 100644 --- a/library/std/tests/env.rs +++ b/library/std/tests/env.rs @@ -4,7 +4,10 @@ use std::path::Path; mod common; #[test] -#[cfg_attr(any(target_os = "emscripten", target_os = "wasi", target_env = "sgx"), ignore)] +#[cfg_attr( + any(target_os = "emscripten", target_os = "wasi", target_env = "sgx", target_os = "l4re"), + ignore +)] fn test_self_exe_path() { let path = current_exe(); assert!(path.is_ok()); diff --git a/library/std/tests/pipe_subprocess.rs b/library/std/tests/pipe_subprocess.rs index 9643c3b7bdad8..c14db690224db 100644 --- a/library/std/tests/pipe_subprocess.rs +++ b/library/std/tests/pipe_subprocess.rs @@ -1,6 +1,10 @@ fn main() { - // No `Command` on Miri and emscripten - #[cfg(all(not(miri), any(unix, windows), not(target_os = "emscripten")))] + // No `Command` on Miri, emscripten or L4Re + #[cfg(all( + not(miri), + any(unix, windows), + not(any(target_os = "emscripten", target_os = "l4re")) + ))] { use std::io::{Read, pipe}; use std::{env, process}; diff --git a/library/std/tests/process_spawning.rs b/library/std/tests/process_spawning.rs index 80e712a2388a1..b7a9a1077696b 100644 --- a/library/std/tests/process_spawning.rs +++ b/library/std/tests/process_spawning.rs @@ -7,7 +7,10 @@ mod common; #[test] // Process spawning not supported by Miri, Emscripten and wasi #[cfg_attr(any(miri, target_os = "emscripten", target_os = "wasi"), ignore)] -#[cfg_attr(any(target_os = "tvos", target_os = "watchos"), ignore = "fork is prohibited")] +#[cfg_attr( + any(target_os = "tvos", target_os = "watchos", target_os = "l4re"), + ignore = "fork is prohibited" +)] fn issue_15149() { // If we're the parent, copy our own binary to a new directory. let my_path = env::current_exe().unwrap(); diff --git a/library/std/tests/time.rs b/library/std/tests/time.rs index d6736e25ace18..6d8b4cbfd094f 100644 --- a/library/std/tests/time.rs +++ b/library/std/tests/time.rs @@ -181,6 +181,7 @@ fn system_time_elapsed() { } #[test] +#[cfg_attr(target_os = "l4re", ignore = "No wallclock time support in L4Re")] fn since_epoch() { let ts = SystemTime::now(); let a = ts.duration_since(UNIX_EPOCH + Duration::SECOND).unwrap(); diff --git a/library/unwind/src/lib.rs b/library/unwind/src/lib.rs index 3725375a713dc..eba08aec4d109 100644 --- a/library/unwind/src/lib.rs +++ b/library/unwind/src/lib.rs @@ -19,7 +19,6 @@ cfg_select! { // Windows MSVC no extra unwinder support needed } any( - target_os = "l4re", target_os = "none", target_os = "espidf", target_os = "nuttx", @@ -31,6 +30,7 @@ cfg_select! { windows, target_os = "psp", target_os = "solid_asp3", + target_os = "l4re", all(target_vendor = "fortanix", target_env = "sgx"), all(target_os = "wasi", panic = "unwind"), target_os = "xous", diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs index f4a9b5704a434..8cddd822c806e 100644 --- a/src/bootstrap/src/utils/helpers.rs +++ b/src/bootstrap/src/utils/helpers.rs @@ -227,7 +227,8 @@ pub fn use_host_linker(target: TargetSelection) -> bool { || target.contains("fortanix") || target.contains("fuchsia") || target.contains("bpf") - || target.contains("switch")) + || target.contains("switch") + || target.contains("l4re")) } pub fn target_supports_cranelift_backend(target: TargetSelection) -> bool { From f639348a1b537c5f697161d839b2ce64ce6767be Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 6 Aug 2026 14:12:38 +0300 Subject: [PATCH 26/57] expand: Change feature gate wording for `feature(proc_macro_hygiene)` --- compiler/rustc_expand/src/expand.rs | 2 +- tests/ui/eii/errors.rs | 2 +- tests/ui/eii/errors.stderr | 2 +- tests/ui/macros/issue-111749.rs | 2 +- tests/ui/macros/issue-111749.stderr | 2 +- tests/ui/proc-macro/cfg-eval-fail.rs | 2 +- tests/ui/proc-macro/cfg-eval-fail.stderr | 2 +- tests/ui/proc-macro/proc-macro-gates.rs | 12 ++++++------ tests/ui/proc-macro/proc-macro-gates.stderr | 12 ++++++------ 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 4846b48af8d5e..c80ffe625f248 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1046,7 +1046,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { self.cx.sess, sym::proc_macro_hygiene, span, - format!("custom attributes cannot be applied to {kind}"), + format!("macro attributes on {kind} are unstable"), ) .emit(); } diff --git a/tests/ui/eii/errors.rs b/tests/ui/eii/errors.rs index 3b28e268662ef..b3bb4cc031bd8 100644 --- a/tests/ui/eii/errors.rs +++ b/tests/ui/eii/errors.rs @@ -8,7 +8,7 @@ #[eii_declaration(bar)] //~ ERROR `#[eii_declaration(...)]` is only valid on macros fn hello() { #[eii_declaration(bar)] //~ ERROR `#[eii_declaration(...)]` is only valid on macros - let x = 3 + 3; //~| ERROR custom attributes cannot be applied to statements + let x = 3 + 3; //~| ERROR macro attributes on statements are unstable } #[eii_declaration] //~ ERROR `#[eii_declaration(...)]` expects a list of one or two elements diff --git a/tests/ui/eii/errors.stderr b/tests/ui/eii/errors.stderr index 512cd135de4c3..28411cfd5108a 100644 --- a/tests/ui/eii/errors.stderr +++ b/tests/ui/eii/errors.stderr @@ -4,7 +4,7 @@ error: `#[eii_declaration(...)]` is only valid on macros LL | #[eii_declaration(bar)] | ^^^^^^^^^^^^^^^^^^^^^^^ -error[E0658]: custom attributes cannot be applied to statements +error[E0658]: macro attributes on statements are unstable --> $DIR/errors.rs:10:5 | LL | #[eii_declaration(bar)] diff --git a/tests/ui/macros/issue-111749.rs b/tests/ui/macros/issue-111749.rs index 799fee22685ab..7c10038925111 100644 --- a/tests/ui/macros/issue-111749.rs +++ b/tests/ui/macros/issue-111749.rs @@ -9,5 +9,5 @@ fn main() { //~^ ERROR the `test` attribute may only be used on a free function //~| ERROR attribute must be of the form `#[test]` //~| WARNING this was previously accepted by the compiler but is being phased out - //~| ERROR custom attributes cannot be applied to expressions + //~| ERROR macro attributes on expressions are unstable } diff --git a/tests/ui/macros/issue-111749.stderr b/tests/ui/macros/issue-111749.stderr index f2773e7029ab5..3207aa182abec 100644 --- a/tests/ui/macros/issue-111749.stderr +++ b/tests/ui/macros/issue-111749.stderr @@ -1,4 +1,4 @@ -error[E0658]: custom attributes cannot be applied to expressions +error[E0658]: macro attributes on expressions are unstable --> $DIR/issue-111749.rs:8:17 | LL | cbor_map! { #[test(test)] 4i32}; diff --git a/tests/ui/proc-macro/cfg-eval-fail.rs b/tests/ui/proc-macro/cfg-eval-fail.rs index 2cde895f2ea44..d9256cfa3377d 100644 --- a/tests/ui/proc-macro/cfg-eval-fail.rs +++ b/tests/ui/proc-macro/cfg-eval-fail.rs @@ -4,5 +4,5 @@ fn main() { let _ = #[cfg_eval] #[cfg(false)] 0; //~^ ERROR removing an expression is not supported in this position - //~| ERROR custom attributes cannot be applied to expressions + //~| ERROR macro attributes on expressions are unstable } diff --git a/tests/ui/proc-macro/cfg-eval-fail.stderr b/tests/ui/proc-macro/cfg-eval-fail.stderr index 61da346fa69f6..6cd3f54d6fafd 100644 --- a/tests/ui/proc-macro/cfg-eval-fail.stderr +++ b/tests/ui/proc-macro/cfg-eval-fail.stderr @@ -4,7 +4,7 @@ error: removing an expression is not supported in this position LL | let _ = #[cfg_eval] #[cfg(false)] 0; | ^^^^^^^^^^^^^ -error[E0658]: custom attributes cannot be applied to expressions +error[E0658]: macro attributes on expressions are unstable --> $DIR/cfg-eval-fail.rs:5:13 | LL | let _ = #[cfg_eval] #[cfg(false)] 0; diff --git a/tests/ui/proc-macro/proc-macro-gates.rs b/tests/ui/proc-macro/proc-macro-gates.rs index 04e097eb2f745..a201836851761 100644 --- a/tests/ui/proc-macro/proc-macro-gates.rs +++ b/tests/ui/proc-macro/proc-macro-gates.rs @@ -23,26 +23,26 @@ fn attrs() { struct S; // Statement, macro - #[empty_attr] //~ ERROR: custom attributes cannot be applied to statements + #[empty_attr] //~ ERROR: macro attributes on statements are unstable println!(); // Statement, semi - #[empty_attr] //~ ERROR: custom attributes cannot be applied to statements + #[empty_attr] //~ ERROR: macro attributes on statements are unstable S; // Statement, local - #[empty_attr] //~ ERROR: custom attributes cannot be applied to statements + #[empty_attr] //~ ERROR: macro attributes on statements are unstable let _x = 2; // Expr - let _x = #[identity_attr] 2; //~ ERROR: custom attributes cannot be applied to expressions + let _x = #[identity_attr] 2; //~ ERROR: macro attributes on expressions are unstable // Opt expr - let _x = [#[identity_attr] 2]; //~ ERROR: custom attributes cannot be applied to expressions + let _x = [#[identity_attr] 2]; //~ ERROR: macro attributes on expressions are unstable // Expr macro let _x = #[identity_attr] println!(); - //~^ ERROR: custom attributes cannot be applied to expressions + //~^ ERROR: macro attributes on expressions are unstable } fn test_case() { diff --git a/tests/ui/proc-macro/proc-macro-gates.stderr b/tests/ui/proc-macro/proc-macro-gates.stderr index 3607b062a5fcb..9a243f4f900b0 100644 --- a/tests/ui/proc-macro/proc-macro-gates.stderr +++ b/tests/ui/proc-macro/proc-macro-gates.stderr @@ -24,7 +24,7 @@ error: key-value macro attributes are not supported LL | #[empty_attr = "y"] | ^^^^^^^^^^^^^^^^^^^ -error[E0658]: custom attributes cannot be applied to statements +error[E0658]: macro attributes on statements are unstable --> $DIR/proc-macro-gates.rs:26:5 | LL | #[empty_attr] @@ -34,7 +34,7 @@ LL | #[empty_attr] = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: custom attributes cannot be applied to statements +error[E0658]: macro attributes on statements are unstable --> $DIR/proc-macro-gates.rs:30:5 | LL | #[empty_attr] @@ -44,7 +44,7 @@ LL | #[empty_attr] = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: custom attributes cannot be applied to statements +error[E0658]: macro attributes on statements are unstable --> $DIR/proc-macro-gates.rs:34:5 | LL | #[empty_attr] @@ -54,7 +54,7 @@ LL | #[empty_attr] = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: custom attributes cannot be applied to expressions +error[E0658]: macro attributes on expressions are unstable --> $DIR/proc-macro-gates.rs:38:14 | LL | let _x = #[identity_attr] 2; @@ -64,7 +64,7 @@ LL | let _x = #[identity_attr] 2; = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: custom attributes cannot be applied to expressions +error[E0658]: macro attributes on expressions are unstable --> $DIR/proc-macro-gates.rs:41:15 | LL | let _x = [#[identity_attr] 2]; @@ -74,7 +74,7 @@ LL | let _x = [#[identity_attr] 2]; = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: custom attributes cannot be applied to expressions +error[E0658]: macro attributes on expressions are unstable --> $DIR/proc-macro-gates.rs:44:14 | LL | let _x = #[identity_attr] println!(); From c1af1957b58902bb4b2d7e879c1dc27693ada73d Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 4 Aug 2026 11:48:44 +0200 Subject: [PATCH 27/57] cleanup `DefiningTy::new` The field of `BodyOwnerKind` is computed via the exact same way as this check. --- .../rustc_borrowck/src/universal_regions.rs | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs index 694f29b942e4f..4540193f60baa 100644 --- a/compiler/rustc_borrowck/src/universal_regions.rs +++ b/compiler/rustc_borrowck/src/universal_regions.rs @@ -155,36 +155,28 @@ impl<'tcx> DefiningTy<'tcx> { } } - BodyOwnerKind::Const { .. } | BodyOwnerKind::Static(..) => { - match tcx.def_kind(body_def_id) { - DefKind::AnonConst - if tcx.anon_const_kind(body_def_id) - == ty::AnonConstKind::NonTypeSystemInline => - { - // This is required for `AscribeUserType` canonical query, which will call - // `type_of(inline_const_def_id)`. That `type_of` would inject erased lifetimes - // into borrowck, which is ICE #78174. - // - // As a workaround, inline consts have an additional generic param (`ty` - // below), so that `type_of(inline_const_def_id).substs(substs)` uses the - // proper type with NLL infer vars. - // - // Fetch the actual type from MIR, as `type_of` returns something useless - // like ``. - let body = tcx.mir_promoted(body_def_id).0.borrow(); - let ty = body.local_decls[RETURN_PLACE].ty; - let typeck_root_def_id = tcx.typeck_root_def_id(body_def_id.to_def_id()); - let parent_args = GenericArgs::identity_for_item(tcx, typeck_root_def_id); - let args = - InlineConstArgs::new(tcx, InlineConstArgsParts { parent_args, ty }) - .args; - DefiningTy::InlineConst(body_def_id.to_def_id(), args) - } - _ => { - let args = GenericArgs::identity_for_item(tcx, body_def_id.to_def_id()); - DefiningTy::Const(body_def_id.to_def_id(), args) - } - } + BodyOwnerKind::Const { inline: true } => { + // This is required for `AscribeUserType` canonical query, which will call + // `type_of(inline_const_def_id)`. That `type_of` would inject erased lifetimes + // into borrowck, which is ICE #78174. + // + // As a workaround, inline consts have an additional generic param (`ty` + // below), so that `type_of(inline_const_def_id).substs(substs)` uses the + // proper type with NLL infer vars. + // + // Fetch the actual type from MIR, as `type_of` returns something useless + // like ``. + let body = tcx.mir_promoted(body_def_id).0.borrow(); + let ty = body.local_decls[RETURN_PLACE].ty; + let typeck_root_def_id = tcx.typeck_root_def_id(body_def_id.to_def_id()); + let parent_args = GenericArgs::identity_for_item(tcx, typeck_root_def_id); + let args = InlineConstArgs::new(tcx, InlineConstArgsParts { parent_args, ty }).args; + DefiningTy::InlineConst(body_def_id.to_def_id(), args) + } + + BodyOwnerKind::Const { inline: false } | BodyOwnerKind::Static(..) => { + let args = GenericArgs::identity_for_item(tcx, body_def_id.to_def_id()); + DefiningTy::Const(body_def_id.to_def_id(), args) } BodyOwnerKind::GlobalAsm => DefiningTy::GlobalAsm(body_def_id.to_def_id()), From b326732355c6d6804ea456c7a64026703c021f92 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 4 Aug 2026 11:09:44 +0200 Subject: [PATCH 28/57] make the c_variadic region late bound --- .../rustc_borrowck/src/universal_regions.rs | 95 +++++++++++++------ tests/ui/c-variadic/not-async.stderr | 18 ++-- tests/ui/c-variadic/variadic-ffi-4.stderr | 8 +- .../note-and-explain-ReVar-124973.stderr | 9 +- 4 files changed, 80 insertions(+), 50 deletions(-) diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs index 4540193f60baa..3479224cc5546 100644 --- a/compiler/rustc_borrowck/src/universal_regions.rs +++ b/compiler/rustc_borrowck/src/universal_regions.rs @@ -26,8 +26,8 @@ use rustc_macros::extension; use rustc_middle::mir::RETURN_PLACE; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{ - self, GenericArgs, GenericArgsRef, InlineConstArgs, InlineConstArgsParts, RegionExt, RegionVid, - Ty, TyCtxt, TypeFoldable, TypeVisitableExt, fold_regions, + self, BoundVariableKind, GenericArgs, GenericArgsRef, InlineConstArgs, InlineConstArgsParts, + List, RegionExt, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, fold_regions, }; use rustc_middle::{bug, span_bug}; use rustc_span::{ErrorGuaranteed, kw, sym}; @@ -183,21 +183,52 @@ impl<'tcx> DefiningTy<'tcx> { } } - #[instrument(level = "debug", skip(tcx, c_variadic_region), ret)] - fn inputs_and_output( - self, - tcx: TyCtxt<'tcx>, - c_variadic_region: impl FnOnce() -> ty::Region<'tcx>, - ) -> ty::Binder<'tcx, &'tcx ty::List>> { + /// The bound variables for a given defining type. This differs from their usual bound vars + /// in that closures and coroutine closures have an additional `'env`, while C-variadic + /// functions have an additional region for their implicit `VaList` input. + pub(crate) fn bound_vars(self, tcx: TyCtxt<'tcx>) -> &'tcx List> { + match self { + DefiningTy::Closure(_, args) => { + let closure_sig = args.as_closure().sig(); + let inputs_and_output = closure_sig.inputs_and_output(); + tcx.mk_bound_variable_kinds_from_iter(inputs_and_output.bound_vars().iter().chain( + iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv)), + )) + } + + DefiningTy::CoroutineClosure(_, args) => { + let closure_sig = args.as_coroutine_closure().coroutine_closure_sig(); + tcx.mk_bound_variable_kinds_from_iter(closure_sig.bound_vars().iter().chain( + iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv)), + )) + } + + DefiningTy::FnDef(def_id, _) => { + let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); + if sig.skip_binder().c_variadic() { + // FIXME(#160495): Don't use an anonymous region here + tcx.mk_bound_variable_kinds_from_iter(sig.bound_vars().iter().chain( + iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::Anon)), + )) + } else { + sig.bound_vars() + } + } + + DefiningTy::Coroutine(..) + | DefiningTy::Const(..) + | DefiningTy::InlineConst(..) + | DefiningTy::GlobalAsm(..) => ty::List::empty(), + } + } + + #[instrument(level = "debug", skip(tcx), ret)] + fn inputs_and_output(self, tcx: TyCtxt<'tcx>) -> ty::Binder<'tcx, &'tcx ty::List>> { match self { DefiningTy::Closure(def_id, args) => { let closure_sig = args.as_closure().sig(); let inputs_and_output = closure_sig.inputs_and_output(); - let bound_vars = tcx.mk_bound_variable_kinds_from_iter( - inputs_and_output.bound_vars().iter().chain(iter::once( - ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv), - )), - ); + let bound_vars = self.bound_vars(tcx); let br = ty::BoundRegion { var: ty::BoundVar::from_usize(bound_vars.len() - 1), kind: ty::BoundRegionKind::ClosureEnv, @@ -245,10 +276,7 @@ impl<'tcx> DefiningTy<'tcx> { // Then we wrap it all up into a list of inputs and output. DefiningTy::CoroutineClosure(def_id, args) => { let closure_sig = args.as_coroutine_closure().coroutine_closure_sig(); - let bound_vars = - tcx.mk_bound_variable_kinds_from_iter(closure_sig.bound_vars().iter().chain( - iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv)), - )); + let bound_vars = self.bound_vars(tcx); let br = ty::BoundRegion { var: ty::BoundVar::from_usize(bound_vars.len() - 1), kind: ty::BoundRegionKind::ClosureEnv, @@ -290,17 +318,24 @@ impl<'tcx> DefiningTy<'tcx> { if tcx.fn_sig(def_id).skip_binder().c_variadic() { let va_list_did = tcx.require_lang_item(LangItem::VaList, tcx.def_span(def_id)); - let region = c_variadic_region(); + let bound_vars = self.bound_vars(tcx); + let br = ty::BoundRegion { + var: ty::BoundVar::from_usize(bound_vars.len() - 1), + kind: ty::BoundRegionKind::Anon, + }; + let region = ty::Region::new_bound(tcx, ty::INNERMOST, br); let va_list_ty = tcx.type_of(va_list_did).instantiate(tcx, &[region.into()]).skip_norm_wip(); // The signature needs to follow the order [input_tys, va_list_ty, output_ty] - return inputs_and_output.map_bound(|tys| { - let (output_ty, input_tys) = tys.split_last().unwrap(); + let (output_ty, input_tys) = + inputs_and_output.skip_binder().split_last().unwrap(); + return ty::Binder::bind_with_vars( tcx.mk_type_list_from_iter( input_tys.iter().copied().chain([va_list_ty, *output_ty]), - ) - }); + ), + bound_vars, + ); } inputs_and_output @@ -678,7 +713,9 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { } else { // If this is a closure, coroutine, or inline-const, then the late-bound regions from the enclosing // function/closures are actually external regions to us. For example, here, 'a is not local - // to the closure c (although it is local to the fn foo): + // to the closure c (although it is local to the fn foo). We need to add them as they could be + // explicitly named in this body: + // // fn foo<'a>() { // let c = || { let x: &'a u32 = ...; } // } @@ -708,8 +745,9 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { // on its signature are local. // // We manually loop over `bound_inputs_and_output` instead of using - // `for_each_late_bound_region_in_item` as we may need to add the otherwise - // implicit `ClosureEnv` region. + // `for_each_late_bound_region_in_item` as both closures and function + // definitions have implicit late bound regions. Closures have a `'env` + // regions while c-variadic function definitions have a `&VaList` argument. let bound_inputs_and_output = self.compute_inputs_and_output(&indices, defining_ty); for (idx, bound_var) in bound_inputs_and_output.bound_vars().iter().enumerate() { if let ty::BoundVariableKind::Region(kind) = bound_var { @@ -825,12 +863,7 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { defining_ty: DefiningTy<'tcx>, ) -> ty::Binder<'tcx, &'tcx ty::List>> { let tcx = self.infcx.tcx; - let inputs_and_output = defining_ty.inputs_and_output(tcx, || { - self.infcx.next_nll_region_var(NllRegionVariableOrigin::FreeRegion, || { - RegionCtxt::Free(sym::c_dash_variadic) - }) - }); - + let inputs_and_output = defining_ty.inputs_and_output(tcx); let inputs_and_output = indices.fold_to_region_vids(tcx, inputs_and_output); // FIXME(#129952): We probably want a more principled approach here. diff --git a/tests/ui/c-variadic/not-async.stderr b/tests/ui/c-variadic/not-async.stderr index 921210382236c..9a81e0ce270d6 100644 --- a/tests/ui/c-variadic/not-async.stderr +++ b/tests/ui/c-variadic/not-async.stderr @@ -14,21 +14,19 @@ error[E0700]: hidden type for `impl Future` captures lifetime that --> $DIR/not-async.rs:4:65 | LL | async unsafe extern "C" fn fn_cannot_be_async(x: isize, _: ...) {} - | -^^ - | | - | opaque type defined here - | - = note: hidden type `{async fn body of fn_cannot_be_async()}` captures lifetime `'_` + | ----------------------------------------------------------------^^ + | | | + | | opaque type defined here + | hidden type `{async fn body of fn_cannot_be_async()}` captures the anonymous lifetime as defined here error[E0700]: hidden type for `impl Future` captures lifetime that does not appear in bounds --> $DIR/not-async.rs:11:73 | LL | async unsafe extern "C" fn method_cannot_be_async(x: isize, _: ...) {} - | -^^ - | | - | opaque type defined here - | - = note: hidden type `{async fn body of S::method_cannot_be_async()}` captures lifetime `'_` + | --------------------------------------------------------------------^^ + | | | + | | opaque type defined here + | hidden type `{async fn body of S::method_cannot_be_async()}` captures the anonymous lifetime as defined here error: aborting due to 4 previous errors diff --git a/tests/ui/c-variadic/variadic-ffi-4.stderr b/tests/ui/c-variadic/variadic-ffi-4.stderr index d53f1f527748c..a92a5fd4bf61d 100644 --- a/tests/ui/c-variadic/variadic-ffi-4.stderr +++ b/tests/ui/c-variadic/variadic-ffi-4.stderr @@ -30,9 +30,9 @@ error: lifetime may not live long enough --> $DIR/variadic-ffi-4.rs:21:5 | LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { - | ------- ------- has type `VaList<'1>` + | ------- ------- has type `VaList<'2>` | | - | has type `&mut VaList<'2>` + | has type `&mut VaList<'1>` LL | ap0 = &mut ap1; | ^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'2` | @@ -44,9 +44,9 @@ error: lifetime may not live long enough --> $DIR/variadic-ffi-4.rs:21:5 | LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { - | ------- ------- has type `VaList<'1>` + | ------- ------- has type `VaList<'2>` | | - | has type `&mut VaList<'2>` + | has type `&mut VaList<'1>` LL | ap0 = &mut ap1; | ^^^^^^^^^^^^^^ assignment requires that `'2` must outlive `'1` | diff --git a/tests/ui/inference/note-and-explain-ReVar-124973.stderr b/tests/ui/inference/note-and-explain-ReVar-124973.stderr index 3610fa82754b9..3ba76eb2ece18 100644 --- a/tests/ui/inference/note-and-explain-ReVar-124973.stderr +++ b/tests/ui/inference/note-and-explain-ReVar-124973.stderr @@ -8,11 +8,10 @@ error[E0700]: hidden type for `impl Future` captures lifetime that --> $DIR/note-and-explain-ReVar-124973.rs:3:76 | LL | async unsafe extern "C" fn multiple_named_lifetimes<'a, 'b>(_: u8, _: ...) {} - | -^^ - | | - | opaque type defined here - | - = note: hidden type `{async fn body of multiple_named_lifetimes<'a, 'b>()}` captures lifetime `'_` + | ---------------------------------------------------------------------------^^ + | | | + | | opaque type defined here + | hidden type `{async fn body of multiple_named_lifetimes<'a, 'b>()}` captures the anonymous lifetime as defined here error: aborting due to 2 previous errors From 234a308c1d683770dca4db2f4f613c713dbf8aec Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Thu, 6 Aug 2026 15:20:35 +0300 Subject: [PATCH 29/57] Fix determining wrong fn kind when delegation is inside const arg --- compiler/rustc_hir_analysis/src/delegation.rs | 21 ++++++++-------- .../ui/delegation/wrong-fn-kind-ice-159127.rs | 15 ++++++++++++ .../wrong-fn-kind-ice-159127.stderr | 24 +++++++++++++++++++ 3 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 tests/ui/delegation/wrong-fn-kind-ice-159127.rs create mode 100644 tests/ui/delegation/wrong-fn-kind-ice-159127.stderr diff --git a/compiler/rustc_hir_analysis/src/delegation.rs b/compiler/rustc_hir_analysis/src/delegation.rs index d33a1cf736738..ab34246d9716e 100644 --- a/compiler/rustc_hir_analysis/src/delegation.rs +++ b/compiler/rustc_hir_analysis/src/delegation.rs @@ -2,8 +2,6 @@ //! //! For more information about delegation design, see the tracking issue #118212. -use std::debug_assert_matches; - use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; @@ -104,14 +102,17 @@ enum FnKind { fn fn_kind<'tcx>(tcx: TyCtxt<'tcx>, def_id: impl Into) -> FnKind { let def_id = def_id.into(); - debug_assert_matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn); - - let parent = tcx.parent(def_id); - match tcx.def_kind(parent) { - DefKind::Trait => FnKind::AssocTrait, - DefKind::Impl { of_trait: true } => FnKind::AssocTraitImpl, - DefKind::Impl { of_trait: false } => FnKind::AssocInherentImpl, - _ => FnKind::Free, + match tcx.def_kind(def_id) { + DefKind::Fn => FnKind::Free, + DefKind::AssocFn => match tcx.def_kind(tcx.parent(def_id)) { + DefKind::Trait => FnKind::AssocTrait, + DefKind::Impl { of_trait } => match of_trait { + true => FnKind::AssocTraitImpl, + false => FnKind::AssocInherentImpl, + }, + _ => unreachable!("associated function can only be in trait or impl"), + }, + _ => unreachable!("delegation/signature can be either free or associated function"), } } diff --git a/tests/ui/delegation/wrong-fn-kind-ice-159127.rs b/tests/ui/delegation/wrong-fn-kind-ice-159127.rs new file mode 100644 index 0000000000000..e117826ef099c --- /dev/null +++ b/tests/ui/delegation/wrong-fn-kind-ice-159127.rs @@ -0,0 +1,15 @@ +#![feature(fn_delegation)] +#![feature(min_generic_const_args)] + +impl + core::direct_const_arg!({ + //~^ ERROR: expected type, found `direct_const_arg!()` constant + fn foo() {} + reuse foo::<>as bar; + reuse bar; + //~^ ERROR: the name `bar` is defined multiple times + }) +{ +} + +fn main() {} diff --git a/tests/ui/delegation/wrong-fn-kind-ice-159127.stderr b/tests/ui/delegation/wrong-fn-kind-ice-159127.stderr new file mode 100644 index 0000000000000..bc218f39ab65a --- /dev/null +++ b/tests/ui/delegation/wrong-fn-kind-ice-159127.stderr @@ -0,0 +1,24 @@ +error[E0428]: the name `bar` is defined multiple times + --> $DIR/wrong-fn-kind-ice-159127.rs:9:9 + | +LL | reuse foo::<>as bar; + | -------------------- previous definition of the value `bar` here +LL | reuse bar; + | ^^^^^^^^^^ `bar` redefined here + | + = note: `bar` must be defined only once in the value namespace of this block + +error: expected type, found `direct_const_arg!()` constant + --> $DIR/wrong-fn-kind-ice-159127.rs:5:5 + | +LL | / core::direct_const_arg!({ +LL | | +LL | | fn foo() {} +LL | | reuse foo::<>as bar; +... | +LL | | }) + | |______^ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0428`. From 8bb9f279a9c384960b8c13b5c70d97d1b4d64067 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 5 Aug 2026 00:24:45 +0200 Subject: [PATCH 30/57] refactor handling of target features in Session --- compiler/rustc_ast_lowering/src/asm.rs | 2 +- .../rustc_codegen_cranelift/src/inline_asm.rs | 2 +- compiler/rustc_codegen_cranelift/src/lib.rs | 6 +- compiler/rustc_codegen_gcc/src/lib.rs | 7 +- compiler/rustc_codegen_llvm/src/asm.rs | 4 +- compiler/rustc_codegen_llvm/src/attributes.rs | 4 +- compiler/rustc_codegen_llvm/src/back/write.rs | 2 +- compiler/rustc_codegen_llvm/src/llvm_util.rs | 18 +- .../src/back/link/raw_dylib.rs | 2 +- .../rustc_codegen_ssa/src/back/metadata.rs | 6 +- compiler/rustc_codegen_ssa/src/lib.rs | 10 +- .../rustc_codegen_ssa/src/mir/naked_asm.rs | 2 +- .../rustc_codegen_ssa/src/target_features.rs | 206 +++++++++--------- .../rustc_codegen_ssa/src/traits/backend.rs | 3 +- compiler/rustc_interface/src/util.rs | 38 +++- .../rustc_mir_build/src/check_unsafety.rs | 2 +- .../src/mono_checks/abi_check.rs | 2 +- compiler/rustc_session/src/config/cfg.rs | 2 +- compiler/rustc_session/src/session.rs | 13 +- compiler/rustc_target/src/spec/mod.rs | 4 +- compiler/rustc_target/src/target_features.rs | 30 ++- src/librustdoc/json/conversions.rs | 2 +- src/tools/miri/src/helpers.rs | 2 +- src/tools/miri/src/intrinsics/x86/mod.rs | 2 +- src/tools/miri/src/machine.rs | 4 +- 25 files changed, 200 insertions(+), 175 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/asm.rs b/compiler/rustc_ast_lowering/src/asm.rs index c6124fdfff38c..fd3a00d56fe0b 100644 --- a/compiler/rustc_ast_lowering/src/asm.rs +++ b/compiler/rustc_ast_lowering/src/asm.rs @@ -93,7 +93,7 @@ impl<'hir> LoweringContext<'_, 'hir> { match asm::InlineAsmClobberAbi::parse( asm_arch, &self.tcx.sess.target, - &self.tcx.sess.unstable_target_features, + &self.tcx.sess.internal_target_features, *abi_name, ) { Ok(abi) => { diff --git a/compiler/rustc_codegen_cranelift/src/inline_asm.rs b/compiler/rustc_codegen_cranelift/src/inline_asm.rs index 03fd11afa3f10..0b8eb75972ec0 100644 --- a/compiler/rustc_codegen_cranelift/src/inline_asm.rs +++ b/compiler/rustc_codegen_cranelift/src/inline_asm.rs @@ -404,7 +404,7 @@ impl<'tcx> InlineAssemblyGenerator<'_, 'tcx> { let abi_clobber = InlineAsmClobberAbi::parse( self.arch, &self.tcx.sess.target, - &self.tcx.sess.unstable_target_features, + &self.tcx.sess.internal_target_features, sym::C, ) .unwrap() diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs index ba586f83ba30d..c59b77eae4611 100644 --- a/compiler/rustc_codegen_cranelift/src/lib.rs +++ b/compiler/rustc_codegen_cranelift/src/lib.rs @@ -39,6 +39,7 @@ use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::settings::{self, Configurable}; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_codegen_ssa::{CompiledModules, CrateInfo, TargetConfig, back}; +use rustc_data_structures::unord::UnordSet; use rustc_log::tracing::info; use rustc_middle::dep_graph::WorkProductMap; use rustc_session::Session; @@ -170,8 +171,6 @@ impl CodegenBackend for CraneliftCodegenBackend { }, _ => vec![], }; - // FIXME do `unstable_target_features` properly - let unstable_target_features = target_features.clone(); // FIXME(f16_f128): `rustc_codegen_llvm` currently disables support on Windows GNU // targets due to GCC using a different ABI than LLVM. Therefore `f16` and `f128` @@ -186,8 +185,7 @@ impl CodegenBackend for CraneliftCodegenBackend { let has_reliable_f128_math = has_reliable_f16_f128 && sess.target.env == Env::Gnu; TargetConfig { - target_features, - unstable_target_features, + internal_target_features: UnordSet::from_iter(target_features), // `rustc_codegen_cranelift` polyfills functionality not yet // available in Cranelift. has_reliable_f16: has_reliable_f16_f128, diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index 55c721a9706a6..621ee4ce27636 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -85,7 +85,7 @@ use rustc_codegen_ssa::back::write::{ CodegenContext, FatLtoInput, ModuleConfig, SharedEmitter, TargetMachineFactoryFn, ThinLtoInput, }; use rustc_codegen_ssa::base::codegen_crate; -use rustc_codegen_ssa::target_features::cfg_target_feature; +use rustc_codegen_ssa::target_features::internal_target_features; use rustc_codegen_ssa::traits::{CodegenBackend, ExtraBackendMethods, WriteBackendMethods}; use rustc_codegen_ssa::{CompiledModule, CompiledModules, CrateInfo, ModuleCodegen, TargetConfig}; use rustc_data_structures::profiling::SelfProfilerRef; @@ -531,7 +531,7 @@ fn to_gcc_opt_level(optlevel: Option) -> OptimizationLevel { /// Returns the features that should be set in `cfg(target_feature)`. fn target_config(sess: &Session, target_info: &LockedTargetInfo) -> TargetConfig { - let (unstable_target_features, target_features) = cfg_target_feature( + let internal_target_features = internal_target_features( sess, |feature| to_gcc_features(sess, feature), |feature| { @@ -555,8 +555,7 @@ fn target_config(sess: &Session, target_info: &LockedTargetInfo) -> TargetConfig let has_reliable_f128 = target_info.supports_target_dependent_type(CType::Float128); TargetConfig { - target_features, - unstable_target_features, + internal_target_features, // There are no known bugs with GCC support for f16 or f128 has_reliable_f16, has_reliable_f16_math: has_reliable_f16, diff --git a/compiler/rustc_codegen_llvm/src/asm.rs b/compiler/rustc_codegen_llvm/src/asm.rs index d2dfa9a45de8b..fba43bba737e0 100644 --- a/compiler/rustc_codegen_llvm/src/asm.rs +++ b/compiler/rustc_codegen_llvm/src/asm.rs @@ -970,14 +970,14 @@ fn dummy_output_type<'ll>(cx: &CodegenCx<'ll, '_>, reg: InlineAsmRegClass) -> &' Hexagon(HexagonInlineAsmRegClass::vreg) => { // HVX vector register size depends on the HVX mode. // LLVM's "v" constraint requires the exact vector width. - if cx.tcx.sess.unstable_target_features.contains(&sym::hvx_length128b) { + if cx.tcx.sess.internal_target_features.contains(&sym::hvx_length128b) { cx.type_vector(cx.type_i32(), 32) // 1024-bit for 128B mode } else { cx.type_vector(cx.type_i32(), 16) // 512-bit for 64B mode } } Hexagon(HexagonInlineAsmRegClass::vreg_pair) => { - if cx.tcx.sess.unstable_target_features.contains(&sym::hvx_length128b) { + if cx.tcx.sess.internal_target_features.contains(&sym::hvx_length128b) { cx.type_vector(cx.type_i32(), 64) // 2048-bit for 128B mode } else { cx.type_vector(cx.type_i32(), 32) // 1024-bit for 64B mode diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index deef323a2e1f8..a8a0de1c8d347 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -383,9 +383,9 @@ fn packed_stack_attr<'ll>( // The backchain and softfloat flags can be set via -Ctarget-features=... // or via #[target_features(enable = ...)] so we have to check both possibilities - let have_backchain = sess.unstable_target_features.contains(&sym::backchain) + let have_backchain = sess.internal_target_features.contains(&sym::backchain) || function_attributes.iter().any(|feature| feature.name == sym::backchain); - let have_softfloat = sess.unstable_target_features.contains(&sym::soft_float) + let have_softfloat = sess.internal_target_features.contains(&sym::soft_float) || function_attributes.iter().any(|feature| feature.name == sym::soft_float); // If both, backchain and packedstack, are enabled LLVM cannot generate valid function entry points diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 94883a94f089a..843589fcb265f 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -210,7 +210,7 @@ pub(crate) fn target_machine_factory( let code_model = to_llvm_code_model(sess.code_model()); // This is used to set cfg_has_threads, so all logic must be in this method. - let singlethread = sess.target.singlethread(&sess.target_features); + let singlethread = sess.target.singlethread(&sess.internal_target_features); let triple = SmallCStr::new(&versioned_llvm_target(sess)); let cpu = SmallCStr::new(llvm_util::target_cpu(sess)); diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 9ad14925afb14..ff710cd9f738f 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -7,7 +7,7 @@ use std::{ptr, slice, str}; use libc::c_int; use rustc_codegen_ssa::base::wants_wasm_eh; -use rustc_codegen_ssa::target_features::cfg_target_feature; +use rustc_codegen_ssa::target_features::internal_target_features; use rustc_codegen_ssa::{TargetConfig, target_features}; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::small_c_str::SmallCStr; @@ -314,7 +314,7 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option TargetConfig { let target_machine = create_informational_target_machine(sess, true); - let (unstable_target_features, target_features) = cfg_target_feature( + let internal_target_features = internal_target_features( sess, |feature| { to_llvm_features(sess, feature) @@ -322,9 +322,9 @@ pub(crate) fn target_config(sess: &Session) -> TargetConfig { .unwrap_or_default() }, |feature| { - // This closure determines whether the target CPU has the feature according to LLVM. We do - // *not* consider the `-Ctarget-feature`s here, as that will be handled later in - // `cfg_target_feature`. + // This closure determines whether the target CPU has the feature according to LLVM. We + // do *not* consider the `-Ctarget-feature`s here, as that will be handled later in + // `internal_target_features`. if let Some(feat) = to_llvm_features(sess, feature) { // All the LLVM features this expands to must be enabled. for llvm_feature in feat { @@ -344,8 +344,7 @@ pub(crate) fn target_config(sess: &Session) -> TargetConfig { ); let mut cfg = TargetConfig { - target_features, - unstable_target_features, + internal_target_features, has_reliable_f16: true, has_reliable_f16_math: true, has_reliable_f128: true, @@ -730,7 +729,10 @@ pub(crate) fn global_llvm_features(sess: &Session, only_base_features: bool) -> target_features::flag_to_backend_features(sess, extend_backend_features); } - // We add this in the "base target" so that these show up in `sess.unstable_target_features`. + // `-C` flags that map to LLVM target features. + // We need to include them even with `only_base_features` as this is used to populate + // `sess.internal_target_features` where we very much want them to be present (e.g. the inline + // asm logic uses that to check which registers may be used). llvm_features_by_flags(sess, &mut features); features diff --git a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs index dbc0abdb50da8..f8cc07201d10f 100644 --- a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs +++ b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs @@ -229,7 +229,7 @@ fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport] // It is important that the order of reservation matches the order of writing. // The object crate contains many debug asserts that fire if you get this wrong. - let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.unstable_target_features) + let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.internal_target_features) else { sess.dcx().fatal(format!( "raw-dylib is not supported for the architecture `{}`", diff --git a/compiler/rustc_codegen_ssa/src/back/metadata.rs b/compiler/rustc_codegen_ssa/src/back/metadata.rs index 951a60426b5d5..a43bf72b6a27d 100644 --- a/compiler/rustc_codegen_ssa/src/back/metadata.rs +++ b/compiler/rustc_codegen_ssa/src/back/metadata.rs @@ -207,7 +207,7 @@ pub(crate) fn create_object_file(sess: &Session) -> Option Endianness::Big, }; let Some((architecture, sub_architecture)) = - sess.target.object_architecture(&sess.unstable_target_features) + sess.target.object_architecture(&sess.internal_target_features) else { return None; }; @@ -328,12 +328,12 @@ pub(super) fn elf_e_flags(architecture: Architecture, sess: &Session) -> u32 { let mut e_flags: u32 = 0x0; // Check if compression is enabled - if sess.target_features.contains(&sym::zca) { + if sess.internal_target_features.contains(&sym::zca) { e_flags |= elf::EF_RISCV_RVC; } // Check if RVTSO is enabled - if sess.target_features.contains(&sym::ztso) { + if sess.internal_target_features.contains(&sym::ztso) { e_flags |= elf::EF_RISCV_TSO; } diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 9a42debe1dd97..02ae2d50390cc 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -20,7 +20,7 @@ use std::sync::Arc; use rustc_abi::Size; use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; -use rustc_data_structures::unord::UnordMap; +use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_hir::CRATE_HIR_ID; use rustc_hir::attrs::{CfgEntry, NativeLibKind, WindowsSubsystemKind}; use rustc_hir::def_id::CrateNum; @@ -306,14 +306,12 @@ pub struct CrateInfo { pub exported_symbols_for_lto: Vec, } -/// Target-specific options that get set in `cfg(...)`. +/// Target-specific options that get set in `sess`/`cfg(...)`. /// /// RUSTC_SPECIFIC_FEATURES should be skipped here, those are handled outside codegen. pub struct TargetConfig { - /// Options to be set in `cfg(target_features)`. - pub target_features: Vec, - /// Options to be set in `cfg(target_features)`, but including unstable features. - pub unstable_target_features: Vec, + /// Options to be set in `sess.internal_target_features`. + pub internal_target_features: UnordSet, /// Option for `cfg(target_has_reliable_f16)`, true if `f16` basic arithmetic works. pub has_reliable_f16: bool, /// Option for `cfg(target_has_reliable_f16_math)`, true if `f16` math calls work. diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs index 131a345fe557d..33cc321ea6d32 100644 --- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs +++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs @@ -151,7 +151,7 @@ fn prefix_and_suffix<'tcx>( let asm_binary_format = &tcx.sess.target.binary_format; let is_arm = tcx.sess.target.arch == Arch::Arm; - let is_thumb = tcx.sess.unstable_target_features.contains(&sym::thumb_mode); + let is_thumb = tcx.sess.internal_target_features.contains(&sym::thumb_mode); let function_sections = tcx.sess.opts.unstable_opts.function_sections.unwrap_or(tcx.sess.target.function_sections); diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index 8f459e5a218d2..8db149fc6df5b 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -131,7 +131,7 @@ pub(crate) fn from_target_feature_attr( /// Computes the set of target features used in a function for the purposes of /// inline assembly. fn asm_target_features(tcx: TyCtxt<'_>, did: DefId) -> &FxIndexSet { - let mut target_features = tcx.sess.unstable_target_features.clone(); + let mut target_features = tcx.sess.internal_target_features.clone(); if tcx.def_kind(did).has_codegen_attrs() { let attrs = tcx.codegen_fn_attrs(did); target_features.extend(attrs.target_features.iter().map(|feature| feature.name)); @@ -164,20 +164,22 @@ pub(crate) fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId, } } -/// Parse the value of the target spec `features` field or `-Ctarget-feature`, also expanding -/// implied features, and call the closure for each (expanded) Rust feature. If the list contains -/// a syntactically invalid item (not starting with `+`/`-`), the error callback is invoked. +/// Parse the value of the target spec `features` field or `-Ctarget-feature`, calling the closure +/// for each entry in the list, also expanding implied features (but only for actual Rust target +/// features). If the list contains a syntactically invalid item (not starting with `+`/`-`) , the +/// error callback is invoked. fn parse_rust_feature_list<'a>( sess: &'a Session, features: &'a str, err_callback: impl Fn(&'a str), mut callback: impl FnMut( /* base_feature */ &'a str, - /* with_implied */ FxHashSet<&'a str>, + /* with_implied */ Option>, /* enable */ bool, ), ) { - // A cache for the backwards implication map. + // A cache for the forward and backwards feature maps. + let mut features_map: Option> = None; let mut inverse_implied_features: Option>> = None; for feature in features.split(',') { @@ -187,13 +189,30 @@ fn parse_rust_feature_list<'a>( continue; } - callback(base_feature, sess.target.implied_target_features(base_feature), true) + let features_map = + features_map.get_or_insert_with(|| sess.target.rust_target_features_map()); + + if !features_map.contains_key(&base_feature) { + callback(base_feature, None, true); + continue; + } + + let implied_features = sess.target.implied_target_features(base_feature, &features_map); + callback(base_feature, Some(implied_features), true) } else if let Some(base_feature) = feature.strip_prefix('-') { // Skip features that are not target features, but rustc features. if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) { continue; } + let features_map = + features_map.get_or_insert_with(|| sess.target.rust_target_features_map()); + + if !features_map.contains_key(&base_feature) { + callback(base_feature, None, false); + continue; + } + // If `f1` implies `f2`, then `!f2` implies `!f1` -- this is standard logical // contraposition. So we have to find all the reverse implications of `base_feature` and // disable them, too. @@ -210,10 +229,10 @@ fn parse_rust_feature_list<'a>( // Inverse implied target features have their own inverse implied target features, so we // traverse the map until there are no more features to add. - let mut features = FxHashSet::default(); + let mut implied_features = FxHashSet::default(); let mut new_features = vec![base_feature]; while let Some(new_feature) = new_features.pop() { - if features.insert(new_feature) { + if implied_features.insert(new_feature) { if let Some(implied_features) = inverse_implied_features.get(&new_feature) { #[allow(rustc::potential_query_instability)] new_features.extend(implied_features) @@ -221,16 +240,15 @@ fn parse_rust_feature_list<'a>( } } - callback(base_feature, features, false) + callback(base_feature, Some(implied_features), false) } else if !feature.is_empty() { err_callback(feature) } } } -/// Utility function for a codegen backend to compute `cfg(target_feature)`, or more specifically, -/// to populate `sess.unstable_target_features` and `sess.target_features` (these are the first and -/// 2nd component of the return value, respectively). +/// Utility function for a codegen backend to compute the set of all actually enabled Rust target +/// features (which will be stored in `sess.internal_target_features`). /// /// `to_backend_features` converts a Rust feature name into a list of backend feature names; this is /// used for diagnostic purposes only. @@ -242,15 +260,15 @@ fn parse_rust_feature_list<'a>( /// to target features. /// /// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled elsewhere. -pub fn cfg_target_feature<'a, const N: usize>( +pub fn internal_target_features<'a, const N: usize>( sess: &Session, to_backend_features: impl Fn(&'a str) -> SmallVec<[&'a str; N]>, mut target_base_has_feature: impl FnMut(&str) -> bool, -) -> (Vec, Vec) { - let known_features = sess.target.rust_target_features(); +) -> UnordSet { + let features_map = sess.target.rust_target_features_map(); - // Compute which of the known target features are enabled in the 'base' target machine. We only - // consider "supported" features; "forbidden" features are not reflected in `cfg` as of now. + // Compute which of the known target features are enabled in the 'base' target machine: for + // every Rust target feature, ask the backend if it is enabled. let mut features: UnordSet = sess .target .rust_target_features() @@ -263,10 +281,14 @@ pub fn cfg_target_feature<'a, const N: usize>( // // Iteration order is irrelevant because we're collecting into an `UnordSet`. #[allow(rustc::potential_query_instability)] - sess.target.implied_target_features(base_feature).into_iter().map(|f| Symbol::intern(f)) + sess.target + .implied_target_features(base_feature, &features_map) + .into_iter() + .map(|f| Symbol::intern(f)) }) .collect(); + // State gathered for "tied features" check. let mut enabled_disabled_features = FxHashMap::default(); // Add enabled and remove disabled features. @@ -278,37 +300,23 @@ pub fn cfg_target_feature<'a, const N: usize>( sess.dcx().emit_warn(diagnostics::UnknownCTargetFeaturePrefix { feature }); }, |base_feature, new_features, enable| { - // Iteration order is irrelevant since this only influences an `FxHashMap`. - #[allow(rustc::potential_query_instability)] - enabled_disabled_features.extend(new_features.iter().map(|&s| (s, enable))); - - // Iteration order is irrelevant since this only influences an `UnordSet`. - #[allow(rustc::potential_query_instability)] - if enable { - features.extend(new_features.into_iter().map(|f| Symbol::intern(f))); - } else { - // Remove `new_features` from `features`. - for new in new_features { - features.remove(&Symbol::intern(new)); - } - } - - // Check feature validity. - let feature_state = known_features.iter().find(|&&(v, _, _)| v == base_feature); - match feature_state { + match features_map.get(base_feature) { None => { - // This is definitely not a valid Rust feature name. Maybe it is a backend - // feature name? If so, give a better error message. - let rust_feature = known_features.iter().find_map(|&(rust_feature, _, _)| { - let backend_features = to_backend_features(rust_feature); - if backend_features.contains(&base_feature) - && !backend_features.contains(&rust_feature) - { - Some(rust_feature) - } else { - None - } - }); + // This is definitely not a valid Rust feature name. We do not add it to + // `features`. Maybe it is a backend feature name? If so, give a better error + // message. + let rust_feature = sess.target.rust_target_features().iter().find_map( + |&(rust_feature, _, _)| { + let backend_features = to_backend_features(rust_feature); + if backend_features.contains(&base_feature) + && !backend_features.contains(&rust_feature) + { + Some(rust_feature) + } else { + None + } + }, + ); let unknown_feature = if let Some(rust_feature) = rust_feature { diagnostics::UnknownCTargetFeature { feature: base_feature, @@ -322,7 +330,25 @@ pub fn cfg_target_feature<'a, const N: usize>( }; sess.dcx().emit_warn(unknown_feature); } - Some((_, stability, _)) => { + Some((stability, _)) => { + let new_features = new_features.unwrap(); + // Add feature to our set -- only if it is actually a recognized feature. + // Iteration order is irrelevant since this only influences an `FxHashMap`. + #[allow(rustc::potential_query_instability)] + enabled_disabled_features.extend(new_features.iter().map(|&s| (s, enable))); + + // Iteration order is irrelevant since this only influences an `UnordSet`. + #[allow(rustc::potential_query_instability)] + if enable { + features.extend(new_features.into_iter().map(|f| Symbol::intern(f))); + } else { + // Remove `new_features` from `features`. + for new in new_features { + features.remove(&Symbol::intern(new)); + } + } + + // Check feature stability. if let Stability::Forbidden { reason, hard_error } = stability { let diag = diagnostics::ForbiddenCTargetFeature { feature: base_feature, @@ -363,34 +389,11 @@ pub fn cfg_target_feature<'a, const N: usize>( }); } - // Filter enabled features based on feature gates. - let f = |allow_unstable| { - sess.target - .rust_target_features() - .iter() - .filter_map(|(feature, gate, _)| { - // The `allow_unstable` set is used by rustc internally to determine which target - // features are truly available, so we want to return even perma-unstable - // "forbidden" features. - if allow_unstable - || (gate.in_cfg() - && (sess.is_nightly_build() - || gate.requires_nightly(/* in_cfg */ true).is_none())) - { - Some(Symbol::intern(feature)) - } else { - None - } - }) - .filter(|feature| features.contains(&feature)) - .collect() - }; - - (f(true), f(false)) + features } /// Given a map from target_features to whether they are enabled or disabled, ensure only valid -/// combinations are allowed. +/// combinations are allowed. Returns `Some` if a violation is found. pub fn check_tied_features( sess: &Session, features: &FxHashMap<&str, bool>, @@ -416,8 +419,6 @@ pub fn target_spec_to_backend_features<'a>( sess: &'a Session, mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool), ) { - let mut rust_features = vec![]; - // This check handles SM versions that defaults (by LLVM) to unsupported (by Rust) PTX ISA versions. // sm_70, sm_72 and sm_75 defaults to PTX ISA versions with major version 6, while sm_80 default to 7.0 if sess.target.arch == Arch::Nvptx64 @@ -426,7 +427,7 @@ pub fn target_spec_to_backend_features<'a>( None | Some("sm_70") | Some("sm_72") | Some("sm_75") ) { - rust_features.push((true, "ptx70")); + extend_backend_features("ptx70", true); } // Compute implied features @@ -435,20 +436,18 @@ pub fn target_spec_to_backend_features<'a>( &sess.target.features, /* err_callback */ |feature| { - panic!("Target spec contains invalid feature {feature}"); + panic!("Target spec contains invalid feature {feature} (missing `+`/`-` prefix)"); }, - |_base_feature, new_features, enable| { - // FIXME emit an error for unknown features like cfg_target_feature would for -Ctarget-feature - rust_features.extend( - UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)), - ); + |base_feature, new_features, enable| { + // FIXME emit an error for unknown features in the target spec like + // internal_target_features would for -Ctarget-feature. + let new_features = + new_features.unwrap_or_else(|| FxHashSet::from_iter(std::iter::once(base_feature))); + for new_feature in UnordSet::from(new_features).to_sorted_stable_ord().iter() { + extend_backend_features(new_feature, enable); + } }, ); - - // Add this to the backend features. - for (enable, feature) in rust_features { - extend_backend_features(feature, enable); - } } /// Translates the `-Ctarget-feature` flag into a backend target feature list. @@ -459,26 +458,22 @@ pub fn flag_to_backend_features<'a>( sess: &'a Session, mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool), ) { - // Compute implied features - let mut rust_features = vec![]; parse_rust_feature_list( sess, &sess.opts.cg.target_feature, /* err_callback */ |_feature| { - // Errors are already emitted in `cfg_target_feature`; avoid duplicates. + // Errors are already emitted in `internal_target_features`; avoid duplicates. }, - |_base_feature, new_features, enable| { - rust_features.extend( - UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)), - ); + |base_feature, new_features, enable| { + // Forward unknown features to the backend as that's what we have always done. + let new_features = + new_features.unwrap_or_else(|| FxHashSet::from_iter(std::iter::once(base_feature))); + for new_feature in UnordSet::from(new_features).to_sorted_stable_ord().iter() { + extend_backend_features(new_feature, enable); + } }, ); - - // Add this to the backend features. - for (enable, feature) in rust_features { - extend_backend_features(feature, enable); - } } /// Computes the backend target features to be added to account for retpoline flags. @@ -553,13 +548,18 @@ pub(crate) fn provide(providers: &mut Providers) { .target .rust_target_features() .iter() - .map(|(a, b, _)| (a.to_string(), *b)) + .map(|(feat, stab, _)| (feat.to_string(), *stab)) .collect() } }, implied_target_features: |tcx, feature: Symbol| { + if tcx.sess.opts.actually_rustdoc { + // We can't handle implication when we are mixing all targets. + return vec![feature]; + } + let features_map = tcx.sess.target.rust_target_features_map(); let feature = feature.as_str(); - UnordSet::from(tcx.sess.target.implied_target_features(feature)) + UnordSet::from(tcx.sess.target.implied_target_features(feature, &features_map)) .into_sorted_stable_ord() .into_iter() .map(|s| Symbol::intern(s)) diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs index 6014f1af4bfc3..1d63490eab654 100644 --- a/compiler/rustc_codegen_ssa/src/traits/backend.rs +++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs @@ -44,8 +44,7 @@ pub trait CodegenBackend { /// `target_feature` and support for unstable float types. fn target_config(&self, _sess: &Session) -> TargetConfig { TargetConfig { - target_features: vec![], - unstable_target_features: vec![], + internal_target_features: Default::default(), // `true` is used as a default so backends need to acknowledge when they do not // support the float types, rather than accidentally quietly skipping all tests. has_reliable_f16: true, diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index 019c7ccfe979a..58a001ab5b1e9 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -11,7 +11,7 @@ use rustc_ast as ast; use rustc_attr_parsing::ShouldEmit; use rustc_codegen_ssa::back::archive::{ArArchiveBuilderBuilder, ArchiveBuilderBuilder}; use rustc_codegen_ssa::back::link::link_binary; -use rustc_codegen_ssa::target_features::cfg_target_feature; +use rustc_codegen_ssa::target_features::internal_target_features; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_codegen_ssa::{CompiledModules, CrateInfo, TargetConfig}; use rustc_data_structures::base_n::{CASE_INSENSITIVE, ToBaseN}; @@ -50,10 +50,27 @@ pub(crate) fn add_configuration( let tf = sym::target_feature; let tf_cfg = codegen_backend.target_config(sess); - sess.unstable_target_features.extend(tf_cfg.unstable_target_features.iter().copied()); - sess.target_features.extend(tf_cfg.target_features.iter().copied()); + // Add some of the target features to `cfg`. + cfg.extend( + sess.target + .rust_target_features() + .iter() + .filter_map(|(feature, gate, _)| { + if gate.in_cfg() + && (sess.is_nightly_build() + || gate.requires_nightly(/* in_cfg */ true).is_none()) + { + Some(Symbol::intern(feature)) + } else { + None + } + }) + .filter(|feature| tf_cfg.internal_target_features.contains(&feature)) + .map(|feature| (sym::target_feature, Some(feature))), + ); - cfg.extend(tf_cfg.target_features.into_iter().map(|feat| (tf, Some(feat)))); + // Store all of them in the session. + sess.internal_target_features.extend(tf_cfg.internal_target_features.into_sorted_stable_ord()); if tf_cfg.has_reliable_f16 { cfg.insert((sym::target_has_reliable_f16, None)); @@ -74,10 +91,10 @@ pub(crate) fn add_configuration( } /// Ensures that all target features required by the ABI are present. -/// Must be called after `unstable_target_features` has been populated! +/// Must be called after `internal_target_features` has been populated! pub(crate) fn check_abi_required_features(sess: &Session) { let abi_feature_constraints = sess.target.abi_required_features(); - // We check this against `unstable_target_features` as that is conveniently already + // We check this against `internal_target_features` as that is conveniently already // back-translated to rustc feature names, taking into account `-Ctarget-cpu` and `-Ctarget-feature`. // Just double-check that the features we care about are actually on our list. for feature in @@ -90,13 +107,13 @@ pub(crate) fn check_abi_required_features(sess: &Session) { } for feature in abi_feature_constraints.required { - if !sess.unstable_target_features.contains(&Symbol::intern(feature)) { + if !sess.internal_target_features.contains(&Symbol::intern(feature)) { sess.dcx() .emit_warn(diagnostics::AbiRequiredTargetFeature { feature, enabled: "enabled" }); } } for feature in abi_feature_constraints.incompatible { - if sess.unstable_target_features.contains(&Symbol::intern(feature)) { + if sess.internal_target_features.contains(&Symbol::intern(feature)) { sess.dcx() .emit_warn(diagnostics::AbiRequiredTargetFeature { feature, enabled: "disabled" }); } @@ -374,7 +391,7 @@ impl CodegenBackend for DummyCodegenBackend { } let abi_required_features = sess.target.abi_required_features(); - let (target_features, unstable_target_features) = cfg_target_feature::<0>( + let internal_target_features = internal_target_features::<0>( sess, |_feature| Default::default(), |feature| { @@ -387,8 +404,7 @@ impl CodegenBackend for DummyCodegenBackend { ); TargetConfig { - target_features, - unstable_target_features, + internal_target_features, has_reliable_f16: true, has_reliable_f16_math: true, has_reliable_f128: true, diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index 70e9129ffee3f..69590fc351320 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -448,7 +448,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { let build_enabled = self .tcx .sess - .target_features + .internal_target_features .iter() .copied() .filter(|feature| missing.contains(feature)) diff --git a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs index 4479ce2dba08b..173595de5c8c2 100644 --- a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs +++ b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs @@ -57,7 +57,7 @@ fn do_check_simd_vector_abi<'tcx>( ) { let codegen_attrs = tcx.codegen_fn_attrs(def_id); let have_feature = |feat: Symbol| { - let target_feats = tcx.sess.unstable_target_features.contains(&feat); + let target_feats = tcx.sess.internal_target_features.contains(&feat); let fn_feats = codegen_attrs.target_features.iter().any(|x| x.name == feat); target_feats || fn_feats }; diff --git a/compiler/rustc_session/src/config/cfg.rs b/compiler/rustc_session/src/config/cfg.rs index 84a26af6b54ce..e5c874503a00f 100644 --- a/compiler/rustc_session/src/config/cfg.rs +++ b/compiler/rustc_session/src/config/cfg.rs @@ -304,7 +304,7 @@ pub(crate) fn default_configuration(sess: &Session) -> Cfg { } } - if !sess.target.singlethread(&sess.target_features) { + if !sess.target.singlethread(&sess.internal_target_features) { ins_none!(sym::target_has_threads); } diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index aea36bf44f28d..ca0bba589f79a 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -375,11 +375,11 @@ pub struct Session { /// Architecture to use for interpreting asm!. pub asm_arch: Option, - /// Set of enabled features for the current target. - pub target_features: FxIndexSet, - - /// Set of enabled features for the current target, including unstable ones. - pub unstable_target_features: FxIndexSet, + /// Set of actually enabled features for the current target, including ones that are not + /// in `cfg(target_feature)` because they are unstable or forbidden. + /// This is used by the compiler itself when it needs to know which target features are actually + /// going to be enabled in the backend. + pub internal_target_features: FxIndexSet, /// The version of the rustc process, possibly including a commit hash and description. pub cfg_version: &'static str, @@ -1388,8 +1388,7 @@ pub fn build_session( ctfe_backtrace, miri_unleashed_features: Lock::new(Default::default()), asm_arch, - target_features: Default::default(), - unstable_target_features: Default::default(), + internal_target_features: Default::default(), cfg_version, using_internal_features, env_depinfo: Default::default(), diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index a747b0aec7b28..9af73eb1e3259 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -3835,7 +3835,7 @@ impl Target { pub fn object_architecture( &self, - unstable_target_features: &FxIndexSet, + internal_target_features: &FxIndexSet, ) -> Option<(object::Architecture, Option)> { use object::Architecture; Some(match self.arch { @@ -3878,7 +3878,7 @@ impl Target { Arch::RiscV32 => (Architecture::Riscv32, None), Arch::RiscV64 => (Architecture::Riscv64, None), Arch::Sparc => { - if unstable_target_features.contains(&sym::v8plus) { + if internal_target_features.contains(&sym::v8plus) { // Target uses V8+, aka EM_SPARC32PLUS, aka 64-bit V9 but in 32-bit mode (Architecture::Sparc32Plus, None) } else { diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index ce09972396e56..9473a583c6586 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -45,7 +45,8 @@ use rustc_span::{Symbol, sym}; use crate::spec::{Arch, FloatAbi, LlvmAbi, RustcAbi, Target}; -/// Features that control behaviour of rustc, rather than the codegen. +/// Features that control behaviour of rustc, rather than the codegen. Not to be included in +/// `cfg(target_feature)`, `sess.internal_target_features`, or the backend's feature list. /// These exist globally and are not in the target-specific lists below. pub const RUSTC_SPECIFIC_FEATURES: &[&str] = &["crt-static"]; @@ -1152,6 +1153,16 @@ impl Target { } } + /// Computes a map mapping each Rust target feature to the features it implies. + pub fn rust_target_features_map( + &self, + ) -> FxHashMap<&'static str, (Stability, ImpliedFeatures)> { + self.rust_target_features() + .iter() + .map(|&(f, s, i)| (f, (s, i))) + .collect::>() + } + pub fn features_for_correct_fixed_length_vector_abi(&self) -> &'static [(u64, &'static str)] { match &self.arch { Arch::X86 | Arch::X86_64 => X86_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI, @@ -1194,19 +1205,22 @@ impl Target { } // Note: the returned set includes `base_feature`. - pub fn implied_target_features<'a>(&self, base_feature: &'a str) -> FxHashSet<&'a str> { - let implied_features = - self.rust_target_features().iter().map(|(f, _, i)| (f, i)).collect::>(); - + #[track_caller] + pub fn implied_target_features<'a>( + &self, + base_feature: &'a str, + target_features_map: &FxHashMap<&'static str, (Stability, ImpliedFeatures)>, + ) -> FxHashSet<&'a str> { // Implied target features have their own implied target features, so we traverse the // map until there are no more features to add. let mut features = FxHashSet::default(); let mut new_features = vec![base_feature]; while let Some(new_feature) = new_features.pop() { if features.insert(new_feature) { - if let Some(implied_features) = implied_features.get(&new_feature) { - new_features.extend(implied_features.iter().copied()) - } + let (_, implied_features) = target_features_map + .get(&new_feature) + .unwrap_or_else(|| panic!("encountered non-Rust target feature {new_feature}")); + new_features.extend(implied_features.iter().copied()); } } features diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 7e46b2f593e49..0512a3daac46e 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -1291,7 +1291,7 @@ fn format_integer_type(it: rustc_abi::IntegerType) -> String { pub(super) fn target(sess: &rustc_session::Session) -> Target { // Build a set of which features are enabled on this target let globally_enabled_features: FxHashSet<&str> = - sess.unstable_target_features.iter().map(|name| name.as_str()).collect(); + sess.internal_target_features.iter().map(|name| name.as_str()).collect(); // Build a map of target feature stability by feature name use rustc_target::target_features::Stability; diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 8dc6b5f07b92e..ce66a2b8b29c7 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -945,7 +945,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { target_feature: &str, ) -> InterpResult<'tcx, ()> { let this = self.eval_context_ref(); - if !this.tcx.sess.unstable_target_features.contains(&Symbol::intern(target_feature)) { + if !this.tcx.sess.internal_target_features.contains(&Symbol::intern(target_feature)) { throw_ub_format!( "attempted to call intrinsic `{intrinsic}` that requires missing target feature {target_feature}" ); diff --git a/src/tools/miri/src/intrinsics/x86/mod.rs b/src/tools/miri/src/intrinsics/x86/mod.rs index d76d35cb722bc..25361a6435b0a 100644 --- a/src/tools/miri/src/intrinsics/x86/mod.rs +++ b/src/tools/miri/src/intrinsics/x86/mod.rs @@ -65,7 +65,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "sse2.pause" => { let [] = this.check_shim_sig_unadjusted(link_name, args)?; // Only exhibit the spin-loop hint behavior when SSE2 is enabled. - if this.tcx.sess.unstable_target_features.contains(&Symbol::intern("sse2")) { + if this.tcx.sess.internal_target_features.contains(&Symbol::intern("sse2")) { this.yield_active_thread(); } } diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index f476614992041..4ba10ce4612b4 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -1203,14 +1203,14 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { if attrs .target_features .iter() - .any(|feature| !ecx.tcx.sess.target_features.contains(&feature.name)) + .any(|feature| !ecx.tcx.sess.internal_target_features.contains(&feature.name)) { let unavailable = attrs .target_features .iter() .filter(|&feature| { feature.kind != TargetFeatureKind::Implied - && !ecx.tcx.sess.target_features.contains(&feature.name) + && !ecx.tcx.sess.internal_target_features.contains(&feature.name) }) .fold(String::new(), |mut s, feature| { if !s.is_empty() { From 78de456ed2570e84503931f7cdc7d7351427290b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 6 Aug 2026 14:53:46 +0200 Subject: [PATCH 31/57] derive(Diagnostic): link to proper docs --- compiler/rustc_macros/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index 399f20ebfe1eb..2f4e5606cd555 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -180,7 +180,7 @@ decl_derive!( decl_derive!([Lift, attributes(lift)] => lift::lift_derive); decl_derive!( [Diagnostic, attributes( - // struct attributes + // struct and field attributes diag, help, help_once, @@ -194,7 +194,9 @@ decl_derive!( suggestion, suggestion_short, suggestion_hidden, - suggestion_verbose)] => diagnostics::diagnostic_derive + suggestion_verbose)] => + #[doc = "See "] + diagnostics::diagnostic_derive ); decl_derive!( [Subdiagnostic, attributes( From 6e9475f1bf3baf4a2afc5bbe7937888796aba021 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:18:17 +0200 Subject: [PATCH 32/57] Derive attribute parser debug impls --- compiler/rustc_attr_parsing/src/parser.rs | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index 76587ba9f0ead..5f1fc8ba90d5e 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -315,6 +315,7 @@ impl MetaItemOrLitParser { /// `= value` part /// /// The syntax of `MetaItems` can be found at +#[derive(Debug)] pub struct MetaItemParser { path: OwnedPathParser, args: ArgParser, @@ -325,15 +326,6 @@ pub struct MetaItemParser { args_checked: AtomicBool, } -impl Debug for MetaItemParser { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MetaItemParser") - .field("path", &self.path) - .field("args", &self.args) - .finish() - } -} - impl MetaItemParser { /// For a single-segment meta item, returns its name; otherwise, returns `None`. pub fn ident(&self) -> Option { @@ -385,23 +377,13 @@ impl MetaItemParser { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct NameValueParser { pub eq_span: Span, value: MetaItemLit, pub value_span: Span, } -impl Debug for NameValueParser { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("NameValueParser") - .field("eq_span", &self.eq_span) - .field("value", &self.value) - .field("value_span", &self.value_span) - .finish() - } -} - impl NameValueParser { pub fn value_as_lit(&self) -> &MetaItemLit { &self.value From a1b86a7edfd0365463ea4865bdfdd1ae8f47638e Mon Sep 17 00:00:00 2001 From: rabindra789 Date: Mon, 3 Aug 2026 19:09:42 +0530 Subject: [PATCH 33/57] codegen: classify localized MSVC linker progress as linker_info link.exe progress messages (e.g. "Creating library ...") are detected by matching their English text, which fails when the English language pack is not installed and the output is localized despite VSLANG=1033. Since all actual warnings and errors carry a locale-independent LNK#### code, classify every line without one as linker_info instead of linker_messages. Diagnostics are recognized by their structured form, `LINK : warning LNK####:`: the code must be followed by a `:` that is the second colon in the line, so the matcher cannot accidentally hit file names. The one code-bearing informational line, LNK6004 ("performing full link"), keeps the exception that was previously handled by matching its English text. --- compiler/rustc_codegen_ssa/src/back/link.rs | 42 ++++++++---- .../fake-linker.rs | 22 ++++++ .../msvc-localized-linker-output/main.rs | 1 + .../msvc-localized-linker-output/rmake.rs | 67 +++++++++++++++++++ 4 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 tests/run-make/msvc-localized-linker-output/fake-linker.rs create mode 100644 tests/run-make/msvc-localized-linker-output/main.rs create mode 100644 tests/run-make/msvc-localized-linker-output/rmake.rs diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 8cbf3647f5630..b9e3ba4ab9ada 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -1073,27 +1073,43 @@ fn report_linker_output( escape_string(output.trim().as_bytes()) } + fn has_lnk_code(line: &str) -> bool { + // link.exe diagnostics are structured as `LINK : warning LNK####:` or + // `LINK : fatal error LNK####:`. The code is always followed by a `:` + // that is the second colon in the line, so matching that structure + // instead of scanning for `LNK####` anywhere avoids false positives on + // file names. + let Some((code_colon, _)) = line.match_indices(':').nth(1) else { + return false; + }; + let Some(code) = code_colon.checked_sub(7) else { + return false; + }; + let code = &line.as_bytes()[code..code_colon]; + code.starts_with(b"LNK") && code[3..].iter().all(u8::is_ascii_digit) + } + if is_msvc_link_exe(sess) { info!("inferred MSVC link.exe"); escaped_stdout = for_each(&stdout, |line, output| { - // Hide some progress messages from link.exe that we don't care about. - // See https://github.com/chromium/chromium/blob/bfa41e41145ffc85f041384280caf2949bb7bd72/build/toolchain/win/tool_wrapper.py#L144-L146 - // When incremental linking is enabled and an .ilk exists, but its associated .exe is - // missing, link.exe prints the path of the missing .exe followed by: + // Hide progress messages from link.exe that we don't care about. + // These include localized variants of the English messages (e.g. + // "Creating library ..."), which rustc cannot recognize by text + // without the English language pack. + // See https://github.com/rust-lang/rust/issues/159133 + // When incremental linking is enabled and an .ilk exists, but its + // associated .exe is missing, link.exe prints the path of the + // missing .exe followed by: let ilk_but_no_exe = "not found or not built by the last incremental link; performing full link"; - let trimmed = line.trim_start(); - if trimmed.starts_with("Creating library") - || trimmed.starts_with("Generating code") - || trimmed.starts_with("Finished generating code") - || trimmed.ends_with(ilk_but_no_exe) - { - linker_info += line; - linker_info += "\r\n"; - } else { + // LNK6004 is the one code-bearing line that is still informational. + if has_lnk_code(line) && !line.ends_with(ilk_but_no_exe) { *output += line; *output += "\r\n" + } else { + linker_info += line; + linker_info += "\r\n"; } }); } else if is_macos_linker(sess) { diff --git a/tests/run-make/msvc-localized-linker-output/fake-linker.rs b/tests/run-make/msvc-localized-linker-output/fake-linker.rs new file mode 100644 index 0000000000000..2cbb68c4518bf --- /dev/null +++ b/tests/run-make/msvc-localized-linker-output/fake-linker.rs @@ -0,0 +1,22 @@ +fn main() { + // Simulate a localized (e.g. Japanese) `link.exe`, as printed when the + // English language pack is not installed and `VSLANG=1033` has no effect. + // This is "Creating library foo.dll.lib and object foo.dll.exp" in Japanese. + println!("ライブラリ foo.dll.lib とオブジェクト foo.dll.exp を作成中"); + // A file name containing an `LNK####`-looking fragment must not be + // mistaken for a diagnostic, which is why the matcher requires the + // structured `LINK : warning LNK####:` form. + println!("LNK2001.lib: progress message, not a diagnostic"); + for arg in std::env::args() { + if arg == "run_make_lnk" { + // Real diagnostics are structured as `LINK : warning LNK####:`. + println!("LINK : warning LNK2001: unresolved external symbol foo"); + // The one code-bearing informational line has no `LINK : ` prefix + // and keeps the exception that classifies it as `linker_info`. + println!( + "LNK6004: 'foo.exe' not found or not built by the last incremental link; \ + performing full link" + ); + } + } +} diff --git a/tests/run-make/msvc-localized-linker-output/main.rs b/tests/run-make/msvc-localized-linker-output/main.rs new file mode 100644 index 0000000000000..f328e4d9d04c3 --- /dev/null +++ b/tests/run-make/msvc-localized-linker-output/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/tests/run-make/msvc-localized-linker-output/rmake.rs b/tests/run-make/msvc-localized-linker-output/rmake.rs new file mode 100644 index 0000000000000..3d0e4c0d7b61d --- /dev/null +++ b/tests/run-make/msvc-localized-linker-output/rmake.rs @@ -0,0 +1,67 @@ +//@ only-msvc +//@ ignore-cross-compile (need to run the fake link.exe on the host) + +//! Tests that localized (non-English) MSVC `link.exe` progress messages are +//! classified as `linker_info`, not `linker_messages`. +//! +//! `link.exe` is hardcoded by rustc to run with `VSLANG=1033`, which only works +//! when an English language pack is installed. Without it, messages like +//! "Creating library ..." are printed in another language, and the English +//! string matching that used to detect them fails. Since all real diagnostics +//! carry a locale-independent `LNK####` code, printed in the structured +//! `LINK : warning LNK####:` form, any line without one is informational, no +//! matter the language it was printed in. + +use run_make_support::{bare_rustc, rustc, target}; + +fn main() { + // rustc prepends the sysroot's tools bin directory to the linker's `PATH`, + // which bare names like `link.exe` are resolved against. Put the fake + // `link.exe` there so it wins over the real linker; `-L` below keeps std + // available from the real sysroot. + let fake_sysroot = std::env::current_dir().unwrap().join("fake-sysroot"); + let tools_bin = fake_sysroot.join(format!("lib/rustlib/{}/bin", target())); + std::fs::create_dir_all(&tools_bin).unwrap(); + rustc().arg("fake-linker.rs").output(tools_bin.join("link.exe")).run(); + + let real_libdir = rustc().print("target-libdir").run().stdout_utf8(); + let real_libdir = real_libdir.trim(); + + let fake_link = |extra: &[&str]| { + let mut r = bare_rustc(); + r.input("main.rs") + .output("main") + .arg(format!("--sysroot={}", fake_sysroot.display())) + .arg(format!("-L{real_libdir}")) + // Matched by name against the linker's `PATH`, so the fake in the + // tools bin directory is used instead of the real VS linker. + .arg("-Clinker=link.exe") + // Overrides `rust.lld=true` on CI. + .arg("-Clinker-flavor=msvc"); + for a in extra { + r.arg(a); + } + r + }; + + // The localized progress line must not warn by default. + fake_link(&[]) + .run() + .assert_stderr_not_contains("linker stdout") + .assert_stderr_not_contains("ライブラリ foo.dll.lib とオブジェクト foo.dll.exp を作成中"); + + // It is still visible through `linker_info`, and must not be misclassified + // as `linker_messages`. + fake_link(&["-Wlinker_info", "-Dlinker_messages"]) // Fail if the message is misclassified. + .run() + .assert_stderr_contains("ライブラリ foo.dll.lib とオブジェクト foo.dll.exp を作成中"); + + // Real diagnostics keep their `LNK####` code and still warn. + fake_link(&["-Clink-arg=run_make_lnk"]) + .run() + .assert_stderr_contains( + "warning: linker stdout: LINK : warning LNK2001: unresolved external symbol foo", + ) + // The informational LNK6004 line stays hidden. + .assert_stderr_not_contains("LNK6004"); +} From 6253cca669bf76acdae402f17775101ef894bdb2 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 5 Aug 2026 08:52:33 +0200 Subject: [PATCH 34/57] rename 'forbidden' target features to 'internal-only' --- compiler/rustc_codegen_ssa/src/diagnostics.rs | 4 +- .../rustc_codegen_ssa/src/target_features.rs | 15 ++- compiler/rustc_session/src/session.rs | 2 +- compiler/rustc_target/src/target_features.rs | 104 +++++++++++------- 4 files changed, 77 insertions(+), 48 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/diagnostics.rs b/compiler/rustc_codegen_ssa/src/diagnostics.rs index 6b182d795a9ec..cb746a8eed90d 100644 --- a/compiler/rustc_codegen_ssa/src/diagnostics.rs +++ b/compiler/rustc_codegen_ssa/src/diagnostics.rs @@ -1100,7 +1100,7 @@ pub(crate) struct TargetFeatureSafeTrait { #[derive(Diagnostic)] #[diag("target feature `{$feature}` cannot be enabled with `#[target_feature]`: {$reason}")] -pub(crate) struct ForbiddenTargetFeatureAttr<'a> { +pub(crate) struct InternalOnlyTargetFeatureAttr<'a> { #[primary_span] pub span: Span, pub feature: &'a str, @@ -1233,7 +1233,7 @@ pub(crate) struct UnstableCTargetFeature<'a> { #[derive(Diagnostic)] #[diag("target feature `{$feature}` cannot be {$enabled} with `-Ctarget-feature`: {$reason}")] -pub(crate) struct ForbiddenCTargetFeature<'a> { +pub(crate) struct InternalOnlyCTargetFeature<'a> { pub feature: &'a str, pub enabled: &'a str, pub reason: &'a str, diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index 8db149fc6df5b..7705bb72bd890 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -72,7 +72,7 @@ pub(crate) fn from_target_feature_attr( // Only allow target features whose feature gates have been enabled // and which are permitted to be toggled. if let Err(reason) = stability.toggle_allowed() { - tcx.dcx().emit_err(diagnostics::ForbiddenTargetFeatureAttr { + tcx.dcx().emit_err(diagnostics::InternalOnlyTargetFeatureAttr { span: feature_span, feature: feature_str, reason, @@ -107,7 +107,7 @@ pub(crate) fn from_target_feature_attr( diagnostics::Aarch64SoftfloatNeon, ); } else { - tcx.dcx().emit_err(diagnostics::ForbiddenTargetFeatureAttr { + tcx.dcx().emit_err(diagnostics::InternalOnlyTargetFeatureAttr { span: feature_span, feature: name.as_str(), reason: "this feature is incompatible with the target ABI", @@ -349,8 +349,8 @@ pub fn internal_target_features<'a, const N: usize>( } // Check feature stability. - if let Stability::Forbidden { reason, hard_error } = stability { - let diag = diagnostics::ForbiddenCTargetFeature { + if let Stability::InternalOnly { reason, hard_error } = stability { + let diag = diagnostics::InternalOnlyCTargetFeature { feature: base_feature, enabled: if enable { "enabled" } else { "disabled" }, reason, @@ -528,9 +528,12 @@ pub(crate) fn provide(providers: &mut Providers) { (Stability::Stable, _) | ( Stability::Unstable { .. }, - Stability::Unstable { .. } | Stability::Forbidden { .. }, + Stability::Unstable { .. } | Stability::InternalOnly { .. }, ) - | (Stability::Forbidden { .. }, Stability::Forbidden { .. }) => { + | ( + Stability::InternalOnly { .. }, + Stability::InternalOnly { .. }, + ) => { // The stability in the entry is at least as good as the new // one, just keep it. } diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index ca0bba589f79a..7babc06050335 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -376,7 +376,7 @@ pub struct Session { pub asm_arch: Option, /// Set of actually enabled features for the current target, including ones that are not - /// in `cfg(target_feature)` because they are unstable or forbidden. + /// in `cfg(target_feature)` because they are unstable or internal-only. /// This is used by the compiler itself when it needs to know which target features are actually /// going to be enabled in the backend. pub internal_target_features: FxIndexSet, diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index 9473a583c6586..f1dd2d8191985 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -28,8 +28,8 @@ //! call ABI. For example, disabling the `x87` feature on x86 changes how scalar floats are passed as //! arguments, so letting people toggle that feature would be unsound. To this end, the //! [`Target::abi_required_features`] function computes which target features must and must not be -//! enabled for any given target, and individual features can also be marked as [`Forbidden`]. See -//! for some more context. +//! enabled for any given target, and individual features can also be marked as [`InternalOnly`]. +//! See for some more context. //! //! The one exception to features that change the ABI is features that enable larger vector //! registers. Those are permitted to be listed here. The `*_FOR_CORRECT_VECTOR_ABI` arrays store @@ -70,17 +70,21 @@ pub enum Stability { /// feature gate! Symbol, ), + /// This is not actually something we expose as a "target feature" to our users. + /// We just manage it internally as a target feature since that's how LLVM represents it. /// This feature can not be set via `-Ctarget-feature` or `#[target_feature]`, it can only be /// set in the target spec. It is never set in `cfg(target_feature)`. Used in particular for /// features are actually ABI configuration flags (such as "soft-float" on many targets). - /// However, "forbidden" target features can still sometimes be enabled via `-Ctarget-cpu` or - /// target feature implications (on the Rust/LLVM level). To prevent that, ABI-relevant target - /// features are ideally pinned down (required or forbidden) in - /// [`Target::abi_required_features`]. - Forbidden { + /// + /// However, "internal" target features can still sometimes be enabled or disabled via + /// `-Ctarget-cpu` or Rust/LLVM target feature implications. Make sure nothing implies this + /// target feature and nothing is implied by this target feature (except for other internal-only + /// features). Ideally, ABI-relevant target features are pinned down (marked as required or + /// incompatible) in [`Target::abi_required_features`]. + InternalOnly { reason: &'static str, /// True if this is always an error, false if this can be reported as a warning when set via - /// `-Ctarget-feature`. + /// `-Ctarget-feature` (and a hard error when set via `#[target_feature]`). hard_error: bool, }, } @@ -121,7 +125,9 @@ impl Stability { } } Stability::Stable { .. } => None, - Stability::Forbidden { .. } => panic!("forbidden features should not reach this far"), + Stability::InternalOnly { .. } => { + panic!("internal-only features should not reach this far") + } } } @@ -139,7 +145,7 @@ impl Stability { Stability::Unstable(_) | Stability::CfgStableToggleUnstable(_) | Stability::Stable { .. } => Ok(()), - Stability::Forbidden { reason, hard_error: _ } => Err(reason), + Stability::InternalOnly { reason, hard_error: _ } => Err(reason), } } } @@ -158,7 +164,8 @@ static ARM_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("aes", Unstable(sym::arm_target_feature), &["neon"]), ( "atomics-32", - Stability::Forbidden { + // Not implied by any CPU model or other feature. + Stability::InternalOnly { reason: "unsound because it changes the ABI of atomic operations", hard_error: false, }, @@ -245,7 +252,8 @@ static AARCH64_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ // We forbid directly toggling just `fp-armv8`; it must be toggled with `neon`. ( "fp-armv8", - Stability::Forbidden { reason: "Rust ties `fp-armv8` to `neon`", hard_error: false }, + // Pinned down by [`Target::abi_required_features`] when needed. + Stability::InternalOnly { reason: "Rust ties `fp-armv8` to `neon`", hard_error: false }, &[], ), // FEAT_FP8 @@ -312,7 +320,8 @@ static AARCH64_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("rdm", Stable, &["neon"]), ( "reserve-x18", - Forbidden { reason: "use `-Zfixed-x18` compiler flag instead", hard_error: false }, + // Not implied by any CPU model or other feature; the compiler flag is a target modifier. + InternalOnly { reason: "use `-Zfixed-x18` compiler flag instead", hard_error: false }, &[], ), // FEAT_SB @@ -493,7 +502,8 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("rdseed", Stable, &[]), ( "retpoline-external-thunk", - Stability::Forbidden { + // Not implied by any CPU model or other feature; the compiler flag is a target modifier. + Stability::InternalOnly { reason: "use `-Zretpoline-external-thunk` compiler flag instead", hard_error: false, }, @@ -501,7 +511,8 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ), ( "retpoline-indirect-branches", - Stability::Forbidden { + // Not implied by any CPU model or other feature; the compiler flag is a target modifier. + Stability::InternalOnly { reason: "use `-Zretpoline` compiler flag instead", hard_error: false, }, @@ -509,7 +520,8 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ), ( "retpoline-indirect-calls", - Stability::Forbidden { + // Not implied by any CPU model or other feature; the compiler flag is a target modifier. + Stability::InternalOnly { reason: "use `-Zretpoline` compiler flag instead", hard_error: false, }, @@ -522,7 +534,8 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("sm4", Stable, &["avx2"]), ( "soft-float", - Stability::Forbidden { reason: "use a soft-float target instead", hard_error: false }, + // Pinned down by [`Target::abi_required_features`]. + Stability::InternalOnly { reason: "use a soft-float target instead", hard_error: false }, &[], ), ("sse", Stable, &[]), @@ -586,7 +599,8 @@ static POWERPC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("altivec", Unstable(sym::powerpc_target_feature), &[]), ( "hard-float", - Forbidden { reason: "unsupported ABI-configuration feature", hard_error: false }, + // Pinned down by [`Target::abi_required_features`]. + InternalOnly { reason: "unsupported ABI-configuration feature", hard_error: false }, &[], ), ("msync", Unstable(sym::powerpc_target_feature), &[]), @@ -598,7 +612,12 @@ static POWERPC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("power9-vector", Unstable(sym::powerpc_target_feature), &["power8-vector", "power9-altivec"]), ("power10-vector", Unstable(sym::powerpc_target_feature), &["power9-vector"]), ("quadword-atomics", Unstable(sym::powerpc_target_feature), &[]), - ("spe", Forbidden { reason: "unsupported ABI-configuration feature", hard_error: false }, &[]), + ( + "spe", + // Pinned down by [`Target::abi_required_features`]. + InternalOnly { reason: "unsupported ABI-configuration feature", hard_error: false }, + &[], + ), ("vsx", Unstable(sym::powerpc_target_feature), &["altivec"]), // tidy-alphabetical-end ]; @@ -662,7 +681,8 @@ static RISCV_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("f", CfgStableToggleUnstable(sym::riscv_target_feature), &["zicsr"]), ( "forced-atomics", - Stability::Forbidden { + // Not implied by any CPU model or other feature. + Stability::InternalOnly { reason: "unsound because it changes the ABI of atomic operations", hard_error: false, }, @@ -922,7 +942,8 @@ const IBMZ_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("miscellaneous-extensions-3", Stable, &[]), ("miscellaneous-extensions-4", Stable, &[]), ("nnp-assist", Stable, &["vector"]), - ("soft-float", Forbidden { reason: "unsupported ABI-configuration feature", hard_error: false }, &[]), + // Pinned down by [`Target::abi_required_features`]. + ("soft-float", InternalOnly { reason: "unsupported ABI-configuration feature", hard_error: false }, &[]), ("transactional-execution", Unstable(sym::s390x_target_feature), &[]), ("vector", Stable, &[]), ("vector-enhancements-1", Stable, &["vector"]), @@ -976,7 +997,8 @@ static AVR_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("spmx", Unstable(sym::avr_target_feature), &[]), ( "sram", - Forbidden { reason: "devices that have no SRAM are unsupported", hard_error: false }, + // Pinned down by [`Target::abi_required_features`]. + InternalOnly { reason: "devices that have no SRAM are unsupported", hard_error: false }, &[], ), ("tinyencoding", Unstable(sym::avr_target_feature), &[]), @@ -991,7 +1013,11 @@ const XTENSA_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("interrupt", Unstable(sym::xtensa_target_feature), &["exception"]), ( "windowed", - Forbidden { reason: "windowed changes the Xtensa calling convention", hard_error: false }, + // Pinned down by [`Target::abi_required_features`]. + InternalOnly { + reason: "windowed changes the Xtensa calling convention", + hard_error: false, + }, &["exception"], ), ("loop", Unstable(sym::xtensa_target_feature), &[]), @@ -1018,17 +1044,17 @@ const XTENSA_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ /// IMPORTANT: If you're adding another feature list above, make sure to add it to this iterator! pub fn all_rust_features() -> impl Iterator { std::iter::empty() - .chain(ARM_FEATURES.iter()) - .chain(AARCH64_FEATURES.iter()) - .chain(X86_FEATURES.iter()) - .chain(HEXAGON_FEATURES.iter()) - .chain(POWERPC_FEATURES.iter()) - .chain(MIPS_FEATURES.iter()) - .chain(NVPTX_FEATURES.iter()) - .chain(RISCV_FEATURES.iter()) - .chain(WASM_FEATURES.iter()) - .chain(BPF_FEATURES.iter()) - .chain(XTENSA_FEATURES.iter()) + .chain(ARM_FEATURES) + .chain(AARCH64_FEATURES) + .chain(X86_FEATURES) + .chain(HEXAGON_FEATURES) + .chain(POWERPC_FEATURES) + .chain(MIPS_FEATURES) + .chain(NVPTX_FEATURES) + .chain(RISCV_FEATURES) + .chain(WASM_FEATURES) + .chain(BPF_FEATURES) + .chain(XTENSA_FEATURES) .chain(CSKY_FEATURES) .chain(LOONGARCH_FEATURES) .chain(IBMZ_FEATURES) @@ -1204,7 +1230,7 @@ impl Target { } } - // Note: the returned set includes `base_feature`. + /// Note: the returned set includes `base_feature`. #[track_caller] pub fn implied_target_features<'a>( &self, @@ -1240,7 +1266,7 @@ impl Target { const NOTHING: FeatureConstraints = FeatureConstraints { required: &[], incompatible: &[] }; // Some architectures don't have a clean explicit ABI designation; instead, the ABI is // defined by target features. When that is the case, those target features must be - // "forbidden" in the list above to ensure that there is a consistent answer to the + // "internal-only" in the list above to ensure that there is a consistent answer to the // questions "which ABI is used". match &self.arch { Arch::X86 => { @@ -1315,9 +1341,9 @@ impl Target { // LLVM will use float registers when `fp-armv8` is available, e.g. for // calls to built-ins. The only way to ensure a consistent softfloat ABI // on aarch64 is to never enable `fp-armv8`, so we enforce that. - // In Rust we tie `neon` and `fp-armv8` together, therefore `neon` is the - // feature we have to mark as incompatible. - FeatureConstraints { required: &[], incompatible: &["neon"] } + // In Rust we tie `neon` and `fp-armv8` together, therefore `neon` is also + // marked as incompatible. + FeatureConstraints { required: &[], incompatible: &["neon", "fp-armv8"] } } None => { // Everything else is assumed to use a hardfloat ABI. neon and fp-armv8 must be enabled. From e5004d09b0b8ba484ac59586b3124d44a52cf509 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 5 Aug 2026 14:13:01 +0200 Subject: [PATCH 35/57] ensure that we never toggle internal target features via the attribute --- compiler/rustc_codegen_ssa/src/target_features.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index 7705bb72bd890..69487d2039c31 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -122,7 +122,17 @@ pub(crate) fn from_target_feature_attr( } else { TargetFeatureKind::Enabled }; - target_features.push(TargetFeature { name, kind }) + target_features.push(TargetFeature { name, kind }); + + if !rust_target_features + .get(name.as_str()) + .is_some_and(|s| s.toggle_allowed().is_ok()) + { + tcx.dcx().span_delayed_bug( + feature_span, + format!("internal-only feature {name} should not be toggled by `#[target_feature]`"), + ); + } } } } From b9ad974f2024d36d678830dae707efb180b418a1 Mon Sep 17 00:00:00 2001 From: KR-bluejay Date: Thu, 6 Aug 2026 15:21:36 +0000 Subject: [PATCH 36/57] Fix FutureDropPoll shim for by-move async closures --- .../src/shim/async_destructor_ctor.rs | 17 ++++++++++++++++- .../async-drop/async-drop-future-drop-poll.rs | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/ui/async-await/async-drop/async-drop-future-drop-poll.rs diff --git a/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs b/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs index 1347fd21db057..00cfeeea815c1 100644 --- a/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs +++ b/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs @@ -204,8 +204,23 @@ fn build_adrop_for_coroutine_shim<'tcx>( let ty::Coroutine(coroutine_def_id, impl_args) = impl_ty.kind() else { bug!("build_adrop_for_coroutine_shim not for coroutine impl type: ({:?})", shim); }; + let ty::Coroutine(_, id_args) = *tcx.type_of(*coroutine_def_id).skip_binder().kind() else { + bug!() + }; let source_info = SourceInfo::outermost(span); - let body = tcx.optimized_mir(*coroutine_def_id).future_drop_poll().unwrap(); + + // If the kind tys differ, we must use the by-move body + let def_id = if id_args.as_coroutine().kind_ty() == impl_args.as_coroutine().kind_ty() { + *coroutine_def_id + } else { + assert_eq!( + impl_args.as_coroutine().kind_ty().to_opt_closure_kind().unwrap(), + ty::ClosureKind::FnOnce + ); + + tcx.coroutine_by_move_body_def_id(*coroutine_def_id) + }; + let body = tcx.optimized_mir(def_id).future_drop_poll().unwrap(); let mut body: Body<'tcx> = EarlyBinder::bind(tcx, body.clone()).instantiate(tcx, impl_args).skip_norm_wip(); body.source.instance = ty::InstanceKind::Shim(shim); diff --git a/tests/ui/async-await/async-drop/async-drop-future-drop-poll.rs b/tests/ui/async-await/async-drop/async-drop-future-drop-poll.rs new file mode 100644 index 0000000000000..7886cedd3a10a --- /dev/null +++ b/tests/ui/async-await/async-drop/async-drop-future-drop-poll.rs @@ -0,0 +1,17 @@ +// Regression test for #142559 +//@ build-pass +//@ compile-flags: --crate-type=lib +#![feature(async_drop)] +#![allow(incomplete_features)] + +//@ edition: 2024 + +async fn run(f: impl Fn() -> F) { + f().await; +} + +pub async fn async_drop_async_closure() { + let x = async || async {}.await; + + run(x).await; +} From 08e7eaf9629fb8d325fea229b165e6cc6d173b4e Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:05:10 +0000 Subject: [PATCH 37/57] Remove fragile memchr codegen test --- .../lib-optimizations/memchr-result.rs | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 tests/codegen-llvm/lib-optimizations/memchr-result.rs diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs deleted file mode 100644 index beeab470c08af..0000000000000 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Ensure `memchr` communicates that a returned index is in bounds. -//@ compile-flags: -Copt-level=3 -Zinline-mir=false -//@ only-x86_64 -//@ revisions: llvm-old llvm-new -//@ [llvm-old] max-llvm-major-version: 21 -//@ [llvm-new] min-llvm-version: 22 - -#![crate_type = "lib"] -#![feature(slice_internals)] - -extern crate core; - -use core::slice::memchr::{memchr, memrchr}; - -// CHECK-LABEL: @find_char -#[no_mangle] -pub fn find_char(haystack: &str, needle: char) -> Option { - // llvm-old: call void @llvm.assume - // llvm-new-NOT: phi { i64, i64 } - // CHECK: ret { i64, i64 } - haystack.find(needle) -} - -// CHECK-LABEL: @find_byte -#[no_mangle] -pub fn find_byte(haystack: &[u8], needle: u8) -> Option { - // llvm-new-NOT: panic_bounds_check - // CHECK: ret { i1, i8 } - memchr(needle, haystack).map(|index| haystack[index]) -} - -// CHECK-LABEL: @rfind_byte -#[no_mangle] -pub fn rfind_byte(haystack: &[u8], needle: u8) -> Option { - // CHECK-NOT: panic_bounds_check - // CHECK: ret { i1, i8 } - memrchr(needle, haystack).map(|index| haystack[index]) -} From f98cf4cefe471fc4ad246172b2a4aeaf91d1496f Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 6 Aug 2026 19:04:54 +0200 Subject: [PATCH 38/57] move naked function ui tests --- .../ffi.rs} | 0 .../ffi.stderr} | 2 +- .../inline.rs} | 0 .../inline.stderr} | 8 +++---- .../instruction-set.rs} | 0 .../invalid-attr.rs} | 0 .../invalid-attr.stderr} | 16 +++++++------- .../invalid-repr-attr.rs} | 0 .../invalid-repr-attr.stderr} | 12 +++++----- .../mono-sym-fn.rs} | 0 .../{ => naked-functions}/naked-functions.rs | 0 .../naked-functions.stderr | 0 .../rustic-abi.rs} | 0 .../shim.rs} | 0 .../target-feature.rs} | 0 .../testattrs.rs} | 0 .../testattrs.stderr} | 8 +++---- .../unused.aarch64.stderr} | 0 .../unused.rs} | 0 .../unused.x86_64.stderr} | 22 +++++++++---------- 20 files changed, 34 insertions(+), 34 deletions(-) rename tests/ui/asm/{naked-functions-ffi.rs => naked-functions/ffi.rs} (100%) rename tests/ui/asm/{naked-functions-ffi.stderr => naked-functions/ffi.stderr} (90%) rename tests/ui/asm/{naked-functions-inline.rs => naked-functions/inline.rs} (100%) rename tests/ui/asm/{naked-functions-inline.stderr => naked-functions/inline.stderr} (87%) rename tests/ui/asm/{naked-functions-instruction-set.rs => naked-functions/instruction-set.rs} (100%) rename tests/ui/asm/{naked-invalid-attr.rs => naked-functions/invalid-attr.rs} (100%) rename tests/ui/asm/{naked-invalid-attr.stderr => naked-functions/invalid-attr.stderr} (84%) rename tests/ui/asm/{naked-with-invalid-repr-attr.rs => naked-functions/invalid-repr-attr.rs} (100%) rename tests/ui/asm/{naked-with-invalid-repr-attr.stderr => naked-functions/invalid-repr-attr.stderr} (79%) rename tests/ui/asm/{naked-asm-mono-sym-fn.rs => naked-functions/mono-sym-fn.rs} (100%) rename tests/ui/asm/{ => naked-functions}/naked-functions.rs (100%) rename tests/ui/asm/{ => naked-functions}/naked-functions.stderr (100%) rename tests/ui/asm/{naked-functions-rustic-abi.rs => naked-functions/rustic-abi.rs} (100%) rename tests/ui/asm/{naked-function-shim.rs => naked-functions/shim.rs} (100%) rename tests/ui/asm/{naked-functions-target-feature.rs => naked-functions/target-feature.rs} (100%) rename tests/ui/asm/{naked-functions-testattrs.rs => naked-functions/testattrs.rs} (100%) rename tests/ui/asm/{naked-functions-testattrs.stderr => naked-functions/testattrs.stderr} (85%) rename tests/ui/asm/{naked-functions-unused.aarch64.stderr => naked-functions/unused.aarch64.stderr} (100%) rename tests/ui/asm/{naked-functions-unused.rs => naked-functions/unused.rs} (100%) rename tests/ui/asm/{naked-functions-unused.x86_64.stderr => naked-functions/unused.x86_64.stderr} (83%) diff --git a/tests/ui/asm/naked-functions-ffi.rs b/tests/ui/asm/naked-functions/ffi.rs similarity index 100% rename from tests/ui/asm/naked-functions-ffi.rs rename to tests/ui/asm/naked-functions/ffi.rs diff --git a/tests/ui/asm/naked-functions-ffi.stderr b/tests/ui/asm/naked-functions/ffi.stderr similarity index 90% rename from tests/ui/asm/naked-functions-ffi.stderr rename to tests/ui/asm/naked-functions/ffi.stderr index f7893a3b8de98..63c0f263e45e0 100644 --- a/tests/ui/asm/naked-functions-ffi.stderr +++ b/tests/ui/asm/naked-functions/ffi.stderr @@ -1,5 +1,5 @@ warning: `extern` fn uses type `char`, which is not FFI-safe - --> $DIR/naked-functions-ffi.rs:8:28 + --> $DIR/ffi.rs:8:28 | LL | pub extern "C" fn naked(p: char) -> u128 { | ^^^^ not FFI-safe diff --git a/tests/ui/asm/naked-functions-inline.rs b/tests/ui/asm/naked-functions/inline.rs similarity index 100% rename from tests/ui/asm/naked-functions-inline.rs rename to tests/ui/asm/naked-functions/inline.rs diff --git a/tests/ui/asm/naked-functions-inline.stderr b/tests/ui/asm/naked-functions/inline.stderr similarity index 87% rename from tests/ui/asm/naked-functions-inline.stderr rename to tests/ui/asm/naked-functions/inline.stderr index 68648be72328e..fa44f92040b4a 100644 --- a/tests/ui/asm/naked-functions-inline.stderr +++ b/tests/ui/asm/naked-functions/inline.stderr @@ -1,5 +1,5 @@ error[E0736]: attribute incompatible with `#[unsafe(naked)]` - --> $DIR/naked-functions-inline.rs:13:3 + --> $DIR/inline.rs:13:3 | LL | #[unsafe(naked)] | ---------------- function marked with `#[unsafe(naked)]` here @@ -7,7 +7,7 @@ LL | #[inline] | ^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]` error[E0736]: attribute incompatible with `#[unsafe(naked)]` - --> $DIR/naked-functions-inline.rs:20:3 + --> $DIR/inline.rs:20:3 | LL | #[unsafe(naked)] | ---------------- function marked with `#[unsafe(naked)]` here @@ -15,7 +15,7 @@ LL | #[inline(always)] | ^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]` error[E0736]: attribute incompatible with `#[unsafe(naked)]` - --> $DIR/naked-functions-inline.rs:27:3 + --> $DIR/inline.rs:27:3 | LL | #[unsafe(naked)] | ---------------- function marked with `#[unsafe(naked)]` here @@ -23,7 +23,7 @@ LL | #[inline(never)] | ^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]` error[E0736]: attribute incompatible with `#[unsafe(naked)]` - --> $DIR/naked-functions-inline.rs:34:18 + --> $DIR/inline.rs:34:18 | LL | #[unsafe(naked)] | ---------------- function marked with `#[unsafe(naked)]` here diff --git a/tests/ui/asm/naked-functions-instruction-set.rs b/tests/ui/asm/naked-functions/instruction-set.rs similarity index 100% rename from tests/ui/asm/naked-functions-instruction-set.rs rename to tests/ui/asm/naked-functions/instruction-set.rs diff --git a/tests/ui/asm/naked-invalid-attr.rs b/tests/ui/asm/naked-functions/invalid-attr.rs similarity index 100% rename from tests/ui/asm/naked-invalid-attr.rs rename to tests/ui/asm/naked-functions/invalid-attr.rs diff --git a/tests/ui/asm/naked-invalid-attr.stderr b/tests/ui/asm/naked-functions/invalid-attr.stderr similarity index 84% rename from tests/ui/asm/naked-invalid-attr.stderr rename to tests/ui/asm/naked-functions/invalid-attr.stderr index 0b55dbe0dbcf0..aa1d2fd07d38f 100644 --- a/tests/ui/asm/naked-invalid-attr.stderr +++ b/tests/ui/asm/naked-functions/invalid-attr.stderr @@ -1,11 +1,11 @@ error[E0433]: cannot find module or crate `a` in the crate root - --> $DIR/naked-invalid-attr.rs:57:5 + --> $DIR/invalid-attr.rs:57:5 | LL | #[::a] | ^ use of unresolved module or unlinked crate `a` error: the `naked` attribute cannot be used on crates - --> $DIR/naked-invalid-attr.rs:5:11 + --> $DIR/invalid-attr.rs:5:11 | LL | #![unsafe(naked)] | ^^^^^ @@ -13,7 +13,7 @@ LL | #![unsafe(naked)] = help: the `naked` attribute can only be applied to functions error: the `naked` attribute cannot be used on foreign functions - --> $DIR/naked-invalid-attr.rs:10:14 + --> $DIR/invalid-attr.rs:10:14 | LL | #[unsafe(naked)] | ^^^^^ @@ -21,7 +21,7 @@ LL | #[unsafe(naked)] = help: the `naked` attribute can only be applied to functions with a body error: the `naked` attribute cannot be used on structs - --> $DIR/naked-invalid-attr.rs:14:10 + --> $DIR/invalid-attr.rs:14:10 | LL | #[unsafe(naked)] | ^^^^^ @@ -29,7 +29,7 @@ LL | #[unsafe(naked)] = help: the `naked` attribute can only be applied to functions error: the `naked` attribute cannot be used on struct fields - --> $DIR/naked-invalid-attr.rs:17:14 + --> $DIR/invalid-attr.rs:17:14 | LL | #[unsafe(naked)] | ^^^^^ @@ -37,7 +37,7 @@ LL | #[unsafe(naked)] = help: the `naked` attribute can only be applied to functions error: the `naked` attribute cannot be used on required trait methods - --> $DIR/naked-invalid-attr.rs:23:14 + --> $DIR/invalid-attr.rs:23:14 | LL | #[unsafe(naked)] | ^^^^^ @@ -45,7 +45,7 @@ LL | #[unsafe(naked)] = help: the `naked` attribute can only be applied to functions with a body error: the `naked` attribute cannot be used on closures - --> $DIR/naked-invalid-attr.rs:52:14 + --> $DIR/invalid-attr.rs:52:14 | LL | #[unsafe(naked)] | ^^^^^ @@ -53,7 +53,7 @@ LL | #[unsafe(naked)] = help: the `naked` attribute can be applied to functions and methods error[E0736]: attribute incompatible with `#[unsafe(naked)]` - --> $DIR/naked-invalid-attr.rs:57:3 + --> $DIR/invalid-attr.rs:57:3 | LL | #[::a] | ^^^ the `::a` attribute is incompatible with `#[unsafe(naked)]` diff --git a/tests/ui/asm/naked-with-invalid-repr-attr.rs b/tests/ui/asm/naked-functions/invalid-repr-attr.rs similarity index 100% rename from tests/ui/asm/naked-with-invalid-repr-attr.rs rename to tests/ui/asm/naked-functions/invalid-repr-attr.rs diff --git a/tests/ui/asm/naked-with-invalid-repr-attr.stderr b/tests/ui/asm/naked-functions/invalid-repr-attr.stderr similarity index 79% rename from tests/ui/asm/naked-with-invalid-repr-attr.stderr rename to tests/ui/asm/naked-functions/invalid-repr-attr.stderr index 7f12510b8aad6..5b9482e3297c5 100644 --- a/tests/ui/asm/naked-with-invalid-repr-attr.stderr +++ b/tests/ui/asm/naked-functions/invalid-repr-attr.stderr @@ -1,5 +1,5 @@ error: the `repr(C)` attribute cannot be used on functions - --> $DIR/naked-with-invalid-repr-attr.rs:10:3 + --> $DIR/invalid-repr-attr.rs:10:3 | LL | #[repr(C)] | ^^^^^^^ @@ -7,7 +7,7 @@ LL | #[repr(C)] = help: the `repr(C)` attribute can only be applied to data types error: the `repr(transparent)` attribute cannot be used on functions - --> $DIR/naked-with-invalid-repr-attr.rs:17:3 + --> $DIR/invalid-repr-attr.rs:17:3 | LL | #[repr(transparent)] | ^^^^^^^^^^^^^^^^^ @@ -15,7 +15,7 @@ LL | #[repr(transparent)] = help: the `repr(transparent)` attribute can only be applied to data types error: the `repr(C)` attribute cannot be used on functions - --> $DIR/naked-with-invalid-repr-attr.rs:24:3 + --> $DIR/invalid-repr-attr.rs:24:3 | LL | #[repr(C)] | ^^^^^^^ @@ -23,7 +23,7 @@ LL | #[repr(C)] = help: the `repr(C)` attribute can only be applied to data types error: the `repr(C)` attribute cannot be used on functions - --> $DIR/naked-with-invalid-repr-attr.rs:33:3 + --> $DIR/invalid-repr-attr.rs:33:3 | LL | #[repr(C, packed)] | ^^^^^^^^^^^^^^^ @@ -31,7 +31,7 @@ LL | #[repr(C, packed)] = help: the `repr(C)` attribute can only be applied to data types error: the `repr(packed)` attribute cannot be used on functions - --> $DIR/naked-with-invalid-repr-attr.rs:33:3 + --> $DIR/invalid-repr-attr.rs:33:3 | LL | #[repr(C, packed)] | ^^^^^^^^^^^^^^^ @@ -39,7 +39,7 @@ LL | #[repr(C, packed)] = help: the `repr(packed)` attribute can only be applied to data types error: the `repr(u8)` attribute cannot be used on functions - --> $DIR/naked-with-invalid-repr-attr.rs:41:3 + --> $DIR/invalid-repr-attr.rs:41:3 | LL | #[repr(u8)] | ^^^^^^^^ diff --git a/tests/ui/asm/naked-asm-mono-sym-fn.rs b/tests/ui/asm/naked-functions/mono-sym-fn.rs similarity index 100% rename from tests/ui/asm/naked-asm-mono-sym-fn.rs rename to tests/ui/asm/naked-functions/mono-sym-fn.rs diff --git a/tests/ui/asm/naked-functions.rs b/tests/ui/asm/naked-functions/naked-functions.rs similarity index 100% rename from tests/ui/asm/naked-functions.rs rename to tests/ui/asm/naked-functions/naked-functions.rs diff --git a/tests/ui/asm/naked-functions.stderr b/tests/ui/asm/naked-functions/naked-functions.stderr similarity index 100% rename from tests/ui/asm/naked-functions.stderr rename to tests/ui/asm/naked-functions/naked-functions.stderr diff --git a/tests/ui/asm/naked-functions-rustic-abi.rs b/tests/ui/asm/naked-functions/rustic-abi.rs similarity index 100% rename from tests/ui/asm/naked-functions-rustic-abi.rs rename to tests/ui/asm/naked-functions/rustic-abi.rs diff --git a/tests/ui/asm/naked-function-shim.rs b/tests/ui/asm/naked-functions/shim.rs similarity index 100% rename from tests/ui/asm/naked-function-shim.rs rename to tests/ui/asm/naked-functions/shim.rs diff --git a/tests/ui/asm/naked-functions-target-feature.rs b/tests/ui/asm/naked-functions/target-feature.rs similarity index 100% rename from tests/ui/asm/naked-functions-target-feature.rs rename to tests/ui/asm/naked-functions/target-feature.rs diff --git a/tests/ui/asm/naked-functions-testattrs.rs b/tests/ui/asm/naked-functions/testattrs.rs similarity index 100% rename from tests/ui/asm/naked-functions-testattrs.rs rename to tests/ui/asm/naked-functions/testattrs.rs diff --git a/tests/ui/asm/naked-functions-testattrs.stderr b/tests/ui/asm/naked-functions/testattrs.stderr similarity index 85% rename from tests/ui/asm/naked-functions-testattrs.stderr rename to tests/ui/asm/naked-functions/testattrs.stderr index ad2041ec118b9..5039a202efb74 100644 --- a/tests/ui/asm/naked-functions-testattrs.stderr +++ b/tests/ui/asm/naked-functions/testattrs.stderr @@ -1,5 +1,5 @@ error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes - --> $DIR/naked-functions-testattrs.rs:11:1 + --> $DIR/testattrs.rs:11:1 | LL | #[test] | ------- function marked with testing attribute here @@ -7,7 +7,7 @@ LL | #[unsafe(naked)] | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes - --> $DIR/naked-functions-testattrs.rs:19:1 + --> $DIR/testattrs.rs:19:1 | LL | #[test] | ------- function marked with testing attribute here @@ -15,7 +15,7 @@ LL | #[unsafe(naked)] | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes - --> $DIR/naked-functions-testattrs.rs:27:1 + --> $DIR/testattrs.rs:27:1 | LL | #[test] | ------- function marked with testing attribute here @@ -23,7 +23,7 @@ LL | #[unsafe(naked)] | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes - --> $DIR/naked-functions-testattrs.rs:34:1 + --> $DIR/testattrs.rs:34:1 | LL | #[bench] | -------- function marked with testing attribute here diff --git a/tests/ui/asm/naked-functions-unused.aarch64.stderr b/tests/ui/asm/naked-functions/unused.aarch64.stderr similarity index 100% rename from tests/ui/asm/naked-functions-unused.aarch64.stderr rename to tests/ui/asm/naked-functions/unused.aarch64.stderr diff --git a/tests/ui/asm/naked-functions-unused.rs b/tests/ui/asm/naked-functions/unused.rs similarity index 100% rename from tests/ui/asm/naked-functions-unused.rs rename to tests/ui/asm/naked-functions/unused.rs diff --git a/tests/ui/asm/naked-functions-unused.x86_64.stderr b/tests/ui/asm/naked-functions/unused.x86_64.stderr similarity index 83% rename from tests/ui/asm/naked-functions-unused.x86_64.stderr rename to tests/ui/asm/naked-functions/unused.x86_64.stderr index bfb2923b0b8d6..a41e80fdc50d6 100644 --- a/tests/ui/asm/naked-functions-unused.x86_64.stderr +++ b/tests/ui/asm/naked-functions/unused.x86_64.stderr @@ -1,66 +1,66 @@ error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:16:32 + --> $DIR/unused.rs:16:32 | LL | pub extern "C" fn function(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: the lint level is defined here - --> $DIR/naked-functions-unused.rs:5:9 + --> $DIR/unused.rs:5:9 | LL | #![deny(unused)] | ^^^^^^ = note: `#[deny(unused_variables)]` implied by `#[deny(unused)]` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:16:42 + --> $DIR/unused.rs:16:42 | LL | pub extern "C" fn function(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:27:38 + --> $DIR/unused.rs:27:38 | LL | pub extern "C" fn associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:27:48 + --> $DIR/unused.rs:27:48 | LL | pub extern "C" fn associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:35:41 + --> $DIR/unused.rs:35:41 | LL | pub extern "C" fn method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:35:51 + --> $DIR/unused.rs:35:51 | LL | pub extern "C" fn method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:45:40 + --> $DIR/unused.rs:45:40 | LL | extern "C" fn trait_associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:45:50 + --> $DIR/unused.rs:45:50 | LL | extern "C" fn trait_associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:53:43 + --> $DIR/unused.rs:53:43 | LL | extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:53:53 + --> $DIR/unused.rs:53:53 | LL | extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` From b5c330c30eab0c85cf47892b6cf3d43c921bcdbf Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Thu, 6 Aug 2026 23:38:00 +0200 Subject: [PATCH 39/57] snippet emitter: rework debug impls --- .../src/annotate_snippet_emitter_writer.rs | 35 +++++++++++------- compiler/rustc_span/src/source_map.rs | 36 ++++++++++++++++++- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs index c3c9f26c31571..7e7f72943c7cd 100644 --- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs +++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs @@ -42,7 +42,6 @@ pub struct AnnotateSnippetEmitter { ui_testing: bool, ignored_directories_in_source_blocks: Vec, diagnostic_width: Option, - macro_backtrace: bool, track_diagnostics: bool, terminal_url: TerminalUrl, @@ -51,18 +50,30 @@ pub struct AnnotateSnippetEmitter { impl Debug for AnnotateSnippetEmitter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let AnnotateSnippetEmitter { + dst, + sm, + short_message, + ui_testing, + ignored_directories_in_source_blocks, + diagnostic_width, + macro_backtrace, + track_diagnostics, + terminal_url, + theme, + } = self; + f.debug_struct("AnnotateSnippetEmitter") - .field("short_message", &self.short_message) - .field("ui_testing", &self.ui_testing) - .field( - "ignored_directories_in_source_blocks", - &self.ignored_directories_in_source_blocks, - ) - .field("diagnostic_width", &self.diagnostic_width) - .field("macro_backtrace", &self.macro_backtrace) - .field("track_diagnostics", &self.track_diagnostics) - .field("terminal_url", &self.terminal_url) - .field("theme", &self.theme) + .field("dst", &format_args!("")) + .field("sm", sm) + .field("short_message", short_message) + .field("ui_testing", ui_testing) + .field("ignored_directories_in_source_blocks", ignored_directories_in_source_blocks) + .field("diagnostic_width", diagnostic_width) + .field("macro_backtrace", macro_backtrace) + .field("track_diagnostics", track_diagnostics) + .field("terminal_url", terminal_url) + .field("theme", theme) .finish() } } diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs index 47c933e245d49..80d1bae71ae89 100644 --- a/compiler/rustc_span/src/source_map.rs +++ b/compiler/rustc_span/src/source_map.rs @@ -174,6 +174,18 @@ struct SourceMapFiles { stable_id_to_source_file: UnhashMap>, } +impl std::fmt::Debug for SourceMapFiles { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let SourceMapFiles { source_files, stable_id_to_source_file: _ } = self; + + f.debug_list() + .entries( + source_files.iter().map(|f| f.name.prefer_remapped_unconditionally().to_string()), + ) + .finish() + } +} + /// Used to construct a `SourceMap` with `SourceMap::with_inputs`. pub struct SourceMapInputs { pub file_loader: Box, @@ -203,6 +215,28 @@ pub struct SourceMap { checksum_hash_kind: Option, } +impl std::fmt::Debug for SourceMap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let SourceMap { + files, + file_loader, + path_mapping, + working_dir, + hash_kind, + checksum_hash_kind, + } = self; + + f.debug_struct("SourceMap") + .field("files", files) + .field("file_loader", &format_args!("")) + .field("path_mapping", path_mapping) + .field("working_dir", working_dir) + .field("hash_kind", hash_kind) + .field("checksum_hash_kind", checksum_hash_kind) + .finish() + } +} + impl SourceMap { pub fn new(path_mapping: FilePathMapping) -> SourceMap { Self::with_inputs(SourceMapInputs { @@ -1117,7 +1151,7 @@ pub fn get_source_map() -> Option> { with_session_globals(|session_globals| session_globals.source_map.clone()) } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct FilePathMapping { mapping: Vec<(PathBuf, PathBuf)>, filename_remapping_scopes: RemapPathScopeComponents, From b829f17aa86deac34b7cc0b5b55c553c94e5d6d6 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 11:01:52 +1000 Subject: [PATCH 40/57] Simplify `MaybeTransitiveLiveLocals` It's mostly identical to `MaybeLiveLocals`, and we can delegate most of its operations to `MaybeLiveLocals`. Note that there was a tiny difference between `MaybeLiveLocals::apply_call_return_effect` and `MaybeTransitiveLiveLocals::apply_call_return_effect`: the former uses `state.kill(local)`, the latter used `state.remove(local)`. The two are equivalent so the difference didn't matter, but it does demonstrate the dangers of the code duplication. --- .../rustc_mir_dataflow/src/impls/liveness.rs | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index da2ea948366db..f23ef674ba6dc 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -208,7 +208,8 @@ impl DefUse { } } -/// Like `MaybeLiveLocals`, but does not mark locals as live if they are used in a dead assignment. +/// Like `MaybeLiveLocals` (and layered on top of `MaybeLiveLocals`), but does not mark locals as +/// live if they are used in a dead assignment. /// /// This is basically written for dead store elimination and nothing else. /// @@ -274,12 +275,11 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { const NAME: &'static str = "transitive liveness"; fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain { - // bottom = not live - DenseBitSet::new_empty(body.local_decls.len()) + MaybeLiveLocals.bottom_value(body) } - fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) { - // No variables are live until we observe a use + fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) { + MaybeLiveLocals.initialize_start_block(body, state) } fn apply_primary_statement_effect( @@ -288,6 +288,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { statement: &mir::Statement<'tcx>, location: Location, ) { + // This is the one part of `MaybeTransitiveLiveLocals` that differs from `MaybeLiveLocals`. if let Some(destination) = Self::can_be_removed_if_dead(&statement.kind, &self.always_live, &self.debuginfo_locals) && !state.contains(destination.local) @@ -295,7 +296,8 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { // This store is dead return; } - TransferFunction(state).visit_statement(statement, location); + + MaybeLiveLocals.apply_primary_statement_effect(state, statement, location); } fn apply_primary_terminator_effect( @@ -304,27 +306,15 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { terminator: &mir::Terminator<'tcx>, location: Location, ) { - TransferFunction(state).visit_terminator(terminator, location); + MaybeLiveLocals.apply_primary_terminator_effect(state, terminator, location) } fn apply_call_return_effect( &self, state: &mut Self::Domain, - _block: mir::BasicBlock, + block: mir::BasicBlock, return_places: CallReturnPlaces<'_, 'tcx>, ) { - if let CallReturnPlaces::Yield(resume_place) = return_places { - YieldResumeEffect(state).visit_place( - &resume_place, - PlaceContext::MutatingUse(MutatingUseContext::Yield), - Location::START, - ) - } else { - return_places.for_each(|place| { - if let Some(local) = place.as_local() { - state.remove(local); - } - }); - } + MaybeLiveLocals.apply_call_return_effect(state, block, return_places); } } From 530c82cb96e19e367edb4c3661e762aaedfa3979 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 13:18:30 +1000 Subject: [PATCH 41/57] Rename `TransferFunction` As `LivenessTransferFunction`. This avoids renaming it via a `use` item, which makes things clearer. --- compiler/rustc_mir_dataflow/src/impls/liveness.rs | 12 ++++++------ compiler/rustc_mir_dataflow/src/impls/mod.rs | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index f23ef674ba6dc..dafde78e91ee7 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -24,8 +24,8 @@ use crate::{Analysis, Backward, GenKill}; pub struct MaybeLiveLocals; impl MaybeLiveLocals { - pub fn transfer_function(state: &mut I) -> TransferFunction<'_, I> { - TransferFunction(state) + pub fn transfer_function(state: &mut I) -> LivenessTransferFunction<'_, I> { + LivenessTransferFunction(state) } } @@ -50,7 +50,7 @@ impl<'tcx> Analysis<'tcx> for MaybeLiveLocals { statement: &mir::Statement<'tcx>, location: Location, ) { - TransferFunction(state).visit_statement(statement, location); + LivenessTransferFunction(state).visit_statement(statement, location); } fn apply_primary_terminator_effect( @@ -59,7 +59,7 @@ impl<'tcx> Analysis<'tcx> for MaybeLiveLocals { terminator: &mir::Terminator<'tcx>, location: Location, ) { - TransferFunction(state).visit_terminator(terminator, location); + LivenessTransferFunction(state).visit_terminator(terminator, location); } fn apply_call_return_effect( @@ -84,9 +84,9 @@ impl<'tcx> Analysis<'tcx> for MaybeLiveLocals { } } -pub struct TransferFunction<'a, I>(pub &'a mut I); +pub struct LivenessTransferFunction<'a, I>(pub &'a mut I); -impl<'tcx, I> Visitor<'tcx> for TransferFunction<'_, I> +impl<'tcx, I> Visitor<'tcx> for LivenessTransferFunction<'_, I> where I: GenKill, { diff --git a/compiler/rustc_mir_dataflow/src/impls/mod.rs b/compiler/rustc_mir_dataflow/src/impls/mod.rs index 6d573e1c00e1c..1e12e41ce1fb4 100644 --- a/compiler/rustc_mir_dataflow/src/impls/mod.rs +++ b/compiler/rustc_mir_dataflow/src/impls/mod.rs @@ -9,8 +9,7 @@ pub use self::initialized::{ MaybeUninitializedPlaces, MaybeUninitializedPlacesDomain, }; pub use self::liveness::{ - DefUse, MaybeLiveLocals, MaybeTransitiveLiveLocals, - TransferFunction as LivenessTransferFunction, + DefUse, LivenessTransferFunction, MaybeLiveLocals, MaybeTransitiveLiveLocals, }; pub use self::storage_liveness::{ MaybeRequiresStorage, MaybeStorageDead, MaybeStorageLive, always_storage_live_locals, From 7e95965ac1853b29b7e0aaf7858c363eb52895b8 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 13:19:15 +1000 Subject: [PATCH 42/57] Remove `MaybeLiveLocals::transfer_function` It has only two uses, and it's just a synonym for `LivenessTransferFunction`, which has more uses. --- compiler/rustc_mir_dataflow/src/impls/liveness.rs | 6 ------ compiler/rustc_mir_transform/src/dest_prop.rs | 6 +++--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index dafde78e91ee7..a82fed864400f 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -23,12 +23,6 @@ use crate::{Analysis, Backward, GenKill}; /// [liveness]: https://en.wikipedia.org/wiki/Live_variable_analysis pub struct MaybeLiveLocals; -impl MaybeLiveLocals { - pub fn transfer_function(state: &mut I) -> LivenessTransferFunction<'_, I> { - LivenessTransferFunction(state) - } -} - impl<'tcx> Analysis<'tcx> for MaybeLiveLocals { type Domain = DenseBitSet; type Direction = Backward; diff --git a/compiler/rustc_mir_transform/src/dest_prop.rs b/compiler/rustc_mir_transform/src/dest_prop.rs index e392f856696be..924125404a07a 100644 --- a/compiler/rustc_mir_transform/src/dest_prop.rs +++ b/compiler/rustc_mir_transform/src/dest_prop.rs @@ -144,7 +144,7 @@ use rustc_index::{IndexVec, newtype_index}; use rustc_middle::mir::visit::{MutVisitor, PlaceContext, VisitPlacesWith, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; -use rustc_mir_dataflow::impls::{DefUse, MaybeLiveLocals}; +use rustc_mir_dataflow::impls::{DefUse, LivenessTransferFunction, MaybeLiveLocals}; use rustc_mir_dataflow::points::DenseLocationMap; use rustc_mir_dataflow::{Analysis, EntryStates, GenKill}; use tracing::{debug, trace}; @@ -619,7 +619,7 @@ fn save_as_intervals<'tcx>( state.current = state.current + 1; debug_assert_eq!(state.current, two_step_loc(loc, Effect::Before)); - MaybeLiveLocals::transfer_function(&mut state).visit_terminator(term, loc); + LivenessTransferFunction(&mut state).visit_terminator(term, loc); for (statement_index, stmt) in block_data.statements.iter().enumerate().rev() { let loc = Location { block, statement_index }; @@ -659,7 +659,7 @@ fn save_as_intervals<'tcx>( // the all the writes we manually marked as live in the second half of the statement. state.current = TwoStepIndex::from_u32(state.current.as_u32() + 1); debug_assert_eq!(state.current, two_step_loc(loc, Effect::Before)); - MaybeLiveLocals::transfer_function(&mut state).visit_statement(stmt, loc); + LivenessTransferFunction(&mut state).visit_statement(stmt, loc); } // Cleanup the current block for the next one. From 0ae7d22265d64302c4bde60b34ad927a5525b6a1 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 13:23:05 +1000 Subject: [PATCH 43/57] Remove an unnecessary lifetime --- compiler/rustc_mir_dataflow/src/impls/liveness.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index a82fed864400f..673d170c84789 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -228,7 +228,7 @@ impl<'a> MaybeTransitiveLiveLocals<'a> { pub fn can_be_removed_if_dead<'tcx>( stmt_kind: &StatementKind<'tcx>, always_live: &DenseBitSet, - debuginfo_locals: &'a DenseBitSet, + debuginfo_locals: &DenseBitSet, ) -> Option> { // Compute the place that we are storing to, if any let destination = match stmt_kind { From d1689e22cf97f0e9414f770ae5ec6ba50bdbbbce Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 13:23:29 +1000 Subject: [PATCH 44/57] Fix a typo --- compiler/rustc_mir_dataflow/src/impls/liveness.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index 673d170c84789..ff373e906683a 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -214,7 +214,7 @@ pub struct MaybeTransitiveLiveLocals<'a> { } impl<'a> MaybeTransitiveLiveLocals<'a> { - /// The `always_alive` set is the set of locals to which all stores should unconditionally be + /// The `always_live` set is the set of locals to which all stores should unconditionally be /// considered live. /// /// This should include at least all locals that are ever borrowed. From d7a07f0345486d4f576eab8fb435ba6be5caafbf Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 13:27:23 +1000 Subject: [PATCH 45/57] Remove unused derives on `DefUse` --- compiler/rustc_mir_dataflow/src/impls/liveness.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index ff373e906683a..3208f876af11c 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -130,7 +130,6 @@ impl<'tcx> Visitor<'tcx> for YieldResumeEffect<'_> { } } -#[derive(Eq, PartialEq, Clone)] pub enum DefUse { /// Full write to the local. Def, From a7b542a64b43011f14ea58ce8e5c761b0f4af7c9 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 13:54:22 +1000 Subject: [PATCH 46/57] Remove unnecessary `&` sigils --- compiler/rustc_mir_dataflow/src/impls/liveness.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index 3208f876af11c..5c77827a67165 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -283,7 +283,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { ) { // This is the one part of `MaybeTransitiveLiveLocals` that differs from `MaybeLiveLocals`. if let Some(destination) = - Self::can_be_removed_if_dead(&statement.kind, &self.always_live, &self.debuginfo_locals) + Self::can_be_removed_if_dead(&statement.kind, self.always_live, self.debuginfo_locals) && !state.contains(destination.local) { // This store is dead From 162cba5202c77d583a9cfb1de3b6b3c0d5cf9fbb Mon Sep 17 00:00:00 2001 From: sgasho Date: Thu, 6 Aug 2026 23:16:59 +0000 Subject: [PATCH 47/57] dlopen Offload --- compiler/rustc_codegen_llvm/src/back/write.rs | 28 ++-- .../src/builder/gpu_offload.rs | 2 +- .../rustc_codegen_llvm/src/diagnostics.rs | 13 ++ compiler/rustc_codegen_llvm/src/lib.rs | 20 +++ compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 57 -------- compiler/rustc_codegen_llvm/src/llvm/mod.rs | 2 + .../src/llvm/offload_ffi.rs | 133 ++++++++++++++++++ .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 105 -------------- .../llvm-wrapper/offload/CMakeLists.txt | 27 ++++ .../llvm-wrapper/offload/OffloadWrapper.cpp | 117 +++++++++++++++ src/bootstrap/src/core/build_steps/compile.rs | 8 ++ src/bootstrap/src/core/build_steps/llvm.rs | 92 +++++++++++- src/bootstrap/src/core/builder/mod.rs | 1 + src/bootstrap/src/lib.rs | 6 +- 14 files changed, 435 insertions(+), 176 deletions(-) create mode 100644 compiler/rustc_codegen_llvm/src/llvm/offload_ffi.rs create mode 100644 compiler/rustc_llvm/llvm-wrapper/offload/CMakeLists.txt create mode 100644 compiler/rustc_llvm/llvm-wrapper/offload/OffloadWrapper.cpp diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index edf52e67b434b..6aaefbe82ec2b 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -720,7 +720,11 @@ pub(crate) unsafe fn llvm_optimize( // Here we map the old arguments to the new arguments, with an offset of 1 to make sure // that we don't use the newly added `%dyn_ptr`. unsafe { - llvm::LLVMRustOffloadMapper(old_fn, new_fn, old_args_rebuilt.as_ptr()); + llvm::RustOffloadWrapper::get_instance().llvm_rust_offload_wrapper( + old_fn, + new_fn, + old_args_rebuilt.as_slice(), + ); } llvm::set_linkage(new_fn, llvm::get_linkage(old_fn)); @@ -814,16 +818,16 @@ pub(crate) unsafe fn llvm_optimize( let device_dir = device_path.parent().unwrap(); let device_out = device_dir.join("device.bin"); let device_out_c = path_to_c_string(device_out.as_path()); - unsafe { - // 1) Bundle device module into offload image device.bin (device TM) - let ok = llvm::LLVMRustBundleImages( + // 1) Bundle device module into offload image device.bin (device TM) + let ok = unsafe { + llvm::RustOffloadWrapper::get_instance().llvm_rust_bundle_images( module.module_llvm.llmod(), module.module_llvm.tm.raw(), - device_out_c.as_ptr(), - ); - if !ok || !device_out.exists() { - dcx.emit_err(crate::diagnostics::OffloadBundleImagesFailed); - } + device_out_c.as_c_str(), + ) + }; + if !ok || !device_out.exists() { + dcx.emit_err(crate::diagnostics::OffloadBundleImagesFailed); } } @@ -859,8 +863,10 @@ pub(crate) unsafe fn llvm_optimize( // We create a full clone of our LLVM host module, since we will embed the device IR // into it, and this might break caching or incremental compilation otherwise. let llmod2 = llvm::LLVMCloneModule(module.module_llvm.llmod()); - let ok = - unsafe { llvm::LLVMRustOffloadEmbedBufferInModule(llmod2, device_bin_c.as_ptr()) }; + let ok = unsafe { + llvm::RustOffloadWrapper::get_instance() + .llvm_rust_offload_embed_buffer_in_module(llmod2, device_bin_c.as_c_str()) + }; if !ok { dcx.emit_err(crate::diagnostics::OffloadEmbedFailed); } diff --git a/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs b/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs index 0b009321802cf..3d0bb6fcc48fd 100644 --- a/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs +++ b/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs @@ -296,7 +296,7 @@ struct KernelArgsTy { impl KernelArgsTy { const OFFLOAD_VERSION: u64 = 3; - const FLAGS: u64 = 0; + const FLAGS: u64 = 1 << 6; // Enable StrictBlocksAndThreads const TRIPCOUNT: u64 = 0; fn new_decl<'ll>(cx: &CodegenCx<'ll, '_>) -> &'ll Type { let kernel_arguments_ty = cx.type_named_struct("struct.__tgt_kernel_arguments"); diff --git a/compiler/rustc_codegen_llvm/src/diagnostics.rs b/compiler/rustc_codegen_llvm/src/diagnostics.rs index ea29683b9d289..54f8ffbb881da 100644 --- a/compiler/rustc_codegen_llvm/src/diagnostics.rs +++ b/compiler/rustc_codegen_llvm/src/diagnostics.rs @@ -60,6 +60,19 @@ pub(crate) struct AutoDiffWithoutLto; #[diag("using the autodiff feature requires -Z autodiff=Enable")] pub(crate) struct AutoDiffWithoutEnable; +#[derive(Diagnostic)] +#[diag("failed to load our rust offload backend: {$err}")] +pub(crate) struct RustOffloadComponentUnavailable { + pub err: String, +} + +#[derive(Diagnostic)] +#[diag("rust offload backend not found in the sysroot: {$err}")] +#[note("it will be distributed via rustup in the future")] +pub(crate) struct RustOffloadComponentMissing { + pub err: String, +} + #[derive(Diagnostic)] #[diag( "using the offload feature requires -Z offload=" diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 3ec0495956c4c..fe39fc6b3fca0 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -373,6 +373,26 @@ impl CodegenBackend for LlvmCodegenBackend { } fn codegen_crate<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Box { + use rustc_session::config::Offload; + + if tcx.sess.opts.unstable_opts.offload.contains(&Offload::Device) + || tcx.sess.opts.unstable_opts.offload.iter().any(|o| matches!(o, Offload::Host(_))) + { + match llvm::RustOffloadWrapper::get_or_init(&tcx.sess.opts.sysroot) { + Ok(_) => {} + Err(llvm::RustOffloadLibraryError::NotFound { err }) => { + tcx.sess + .dcx() + .emit_fatal(crate::diagnostics::RustOffloadComponentMissing { err }); + } + Err(llvm::RustOffloadLibraryError::LoadFailed { err }) => { + tcx.sess + .dcx() + .emit_fatal(crate::diagnostics::RustOffloadComponentUnavailable { err }); + } + } + } + Box::new(rustc_codegen_ssa::base::codegen_crate(LlvmCodegenBackend(()), tcx)) } diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 4cc5d326bdc9e..1a60b59a93525 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -1713,63 +1713,6 @@ unsafe extern "C" { ) -> &'a Value; } -#[cfg(feature = "llvm_offload")] -pub(crate) use self::Offload::*; - -#[cfg(feature = "llvm_offload")] -mod Offload { - use super::*; - unsafe extern "C" { - /// Processes the module and writes it in an offload compatible way into a "device.bin" file. - pub(crate) fn LLVMRustBundleImages<'a>( - M: &'a Module, - TM: &'a TargetMachine, - device_bin: *const c_char, - ) -> bool; - pub(crate) unsafe fn LLVMRustOffloadEmbedBufferInModule<'a>( - _M: &'a Module, - _device_bin: *const c_char, - ) -> bool; - pub(crate) fn LLVMRustOffloadMapper<'a>( - OldFn: &'a Value, - NewFn: &'a Value, - RebuiltArgs: *const &Value, - ); - } -} - -#[cfg(not(feature = "llvm_offload"))] -pub(crate) use self::Offload_fallback::*; - -#[cfg(not(feature = "llvm_offload"))] -mod Offload_fallback { - use super::*; - /// Processes the module and writes it in an offload compatible way into a "device.bin" file. - /// Marked as unsafe to match the real offload wrapper which is unsafe due to FFI. - #[allow(unused_unsafe)] - pub(crate) unsafe fn LLVMRustBundleImages<'a>( - _M: &'a Module, - _TM: &'a TargetMachine, - _device_bin: *const c_char, - ) -> bool { - unimplemented!("This rustc version was not built with LLVM Offload support!"); - } - pub(crate) unsafe fn LLVMRustOffloadEmbedBufferInModule<'a>( - _M: &'a Module, - _device_bin: *const c_char, - ) -> bool { - unimplemented!("This rustc version was not built with LLVM Offload support!"); - } - #[allow(unused_unsafe)] - pub(crate) unsafe fn LLVMRustOffloadMapper<'a>( - _OldFn: &'a Value, - _NewFn: &'a Value, - _RebuiltArgs: *const &Value, - ) { - unimplemented!("This rustc version was not built with LLVM Offload support!"); - } -} - // FFI bindings for `DIBuilder` functions in the LLVM-C API. // Try to keep these in the same order as in `llvm/include/llvm-c/DebugInfo.h`. // diff --git a/compiler/rustc_codegen_llvm/src/llvm/mod.rs b/compiler/rustc_codegen_llvm/src/llvm/mod.rs index a2d17e93b4996..eb7a529c0b198 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/mod.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/mod.rs @@ -21,8 +21,10 @@ pub(crate) mod diagnostic; pub(crate) mod enzyme_ffi; mod ffi; mod metadata_kind; +pub(crate) mod offload_ffi; pub(crate) use self::enzyme_ffi::*; +pub(crate) use self::offload_ffi::*; impl LLVMRustResult { pub(crate) fn into_result(self) -> Result<(), ()> { diff --git a/compiler/rustc_codegen_llvm/src/llvm/offload_ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/offload_ffi.rs new file mode 100644 index 0000000000000..46d9320248a9b --- /dev/null +++ b/compiler/rustc_codegen_llvm/src/llvm/offload_ffi.rs @@ -0,0 +1,133 @@ +use std::ffi::{CStr, c_char}; +use std::sync::OnceLock; + +use super::ffi::{Module, TargetMachine, Value}; + +type LLVMRustBundleImagesFn = unsafe extern "C" fn(&Module, &TargetMachine, *const c_char) -> bool; +type LLVMRustOffloadEmbedBufferInModuleFn = unsafe extern "C" fn(&Module, *const c_char) -> bool; +type LLVMRustOffloadMapperFn = unsafe extern "C" fn(&Value, &Value, *const &Value); + +use rustc_session::config::host_tuple; +use rustc_session::filesearch; + +use crate::llvm::LLVMRustVersionMajor; + +pub(crate) struct RustOffloadWrapper { + LLVMRustBundleImages: LLVMRustBundleImagesFn, + LLVMRustOffloadEmbedBufferInModule: LLVMRustOffloadEmbedBufferInModuleFn, + LLVMRustOffloadMapper: LLVMRustOffloadMapperFn, + // Keep the dynamic library loaded while the function pointers are used. + _lib: libloading::Library, +} + +#[derive(Debug)] +pub(crate) enum RustOffloadLibraryError { + NotFound { err: String }, + LoadFailed { err: String }, +} + +impl From for RustOffloadLibraryError { + fn from(err: libloading::Error) -> Self { + Self::LoadFailed { err: format!("{err:?}") } + } +} + +static OFFLOAD_INSTANCE: OnceLock = OnceLock::new(); + +impl RustOffloadWrapper { + pub(crate) fn get_or_init( + sysroot: &rustc_session::config::Sysroot, + ) -> Result<&'static RustOffloadWrapper, RustOffloadLibraryError> { + OFFLOAD_INSTANCE.get_or_try_init(|| { + let w = Self::call_dynamic(sysroot)?; + Ok(w) + }) + } + + pub(crate) fn get_instance() -> &'static RustOffloadWrapper { + OFFLOAD_INSTANCE + .get() + .expect("RustOffloadWrapper not initialized. Call get_or_init with sysroot first.") + } + + pub(crate) unsafe fn llvm_rust_bundle_images( + &self, + m: &Module, + tm: &TargetMachine, + c: &CStr, + ) -> bool { + unsafe { (self.LLVMRustBundleImages)(m, tm, c.as_ptr()) } + } + + pub(crate) unsafe fn llvm_rust_offload_embed_buffer_in_module( + &self, + m: &Module, + i: &CStr, + ) -> bool { + unsafe { (self.LLVMRustOffloadEmbedBufferInModule)(m, i.as_ptr()) } + } + + pub(crate) unsafe fn llvm_rust_offload_wrapper(&self, v1: &Value, v2: &Value, vs: &[&Value]) { + unsafe { (self.LLVMRustOffloadMapper)(v1, v2, vs.as_ptr()) } + } + + fn call_dynamic( + sysroot: &rustc_session::config::Sysroot, + ) -> Result { + let rust_offload_path = Self::get_rust_offload_path(sysroot)?; + let lib = unsafe { libloading::Library::new(rust_offload_path)? }; + + let llvm_rust_bundle_images = + *unsafe { lib.get::(b"LLVMRustBundleImages\0")? }; + let llvm_rust_offload_embed_buffer_in_module = *unsafe { + lib.get::( + b"LLVMRustOffloadEmbedBufferInModule\0", + )? + }; + let llvm_rust_offload_wrapper = + *unsafe { lib.get::(b"LLVMRustOffloadMapper\0")? }; + + Ok(Self { + LLVMRustBundleImages: llvm_rust_bundle_images, + LLVMRustOffloadEmbedBufferInModule: llvm_rust_offload_embed_buffer_in_module, + LLVMRustOffloadMapper: llvm_rust_offload_wrapper, + _lib: lib, + }) + } + + fn get_rust_offload_path( + sysroot: &rustc_session::config::Sysroot, + ) -> Result { + let llvm_version_major = unsafe { LLVMRustVersionMajor() }; + + let path_buf = sysroot + .all_paths() + .find_map(|p| { + let candidate = filesearch::make_target_lib_path(p, host_tuple()) + .join(format!("libRustOffload-{}", llvm_version_major)) + .with_extension(std::env::consts::DLL_EXTENSION); + + candidate.exists().then_some(candidate) + }) + .ok_or_else(|| { + let candidates = sysroot + .all_paths() + .map(|p| p.join("lib").display().to_string()) + .collect::>() + .join("\n* "); + RustOffloadLibraryError::NotFound { + err: format!( + "failed to find a `libRustOffload-{llvm_version_major}` \ + in the sysroot candidates:\n* {candidates}" + ), + } + })?; + + Ok(path_buf + .to_str() + .ok_or_else(|| RustOffloadLibraryError::LoadFailed { + err: format!("invalid UTF-8 in path: {}", path_buf.display()), + })? + .to_string()) + } +} diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index f500041a12d8b..983a506bd4ac6 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -164,111 +164,6 @@ extern "C" bool LLVMRustIsCall(LLVMValueRef V) { return llvm::isa(llvm::unwrap(V)); } -// Some of the functions here rely on LLVM modules that may not always be -// available. As such, we only try to build it in the first place, if -// llvm.offload is enabled. -#ifdef OFFLOAD -static Error writeFile(StringRef Filename, StringRef Data) { - Expected> OutputOrErr = - FileOutputBuffer::create(Filename, Data.size()); - if (!OutputOrErr) - return OutputOrErr.takeError(); - std::unique_ptr Output = std::move(*OutputOrErr); - llvm::copy(Data, Output->getBufferStart()); - if (Error E = Output->commit()) - return E; - return Error::success(); -} - -// This is the first of many steps in creating a binary using llvm offload, -// to run code on the gpu. Concrete, it replaces the following binary use: -// clang-offload-packager -o device.bin -// --image=file=device.bc,triple=amdgcn-amd-amdhsa,arch=gfx90a,kind=openmp -// The input module is the rust code compiled for a gpu target like amdgpu. -// Based on clang/tools/clang-offload-packager/ClangOffloadPackager.cpp -extern "C" bool LLVMRustBundleImages(LLVMModuleRef M, TargetMachine &TM, - const char *HostOutPath) { - std::string Storage; - llvm::raw_string_ostream OS1(Storage); - llvm::WriteBitcodeToFile(*unwrap(M), OS1); - OS1.flush(); - auto MB = llvm::MemoryBuffer::getMemBufferCopy(Storage, "device.bc"); - - SmallVector BinaryData; - raw_svector_ostream OS2(BinaryData); - - OffloadBinary::OffloadingImage ImageBinary{}; - ImageBinary.TheImageKind = object::IMG_Bitcode; - ImageBinary.Image = std::move(MB); - ImageBinary.TheOffloadKind = object::OFK_OpenMP; - - std::string TripleStr = TM.getTargetTriple().str(); - llvm::StringRef CPURef = TM.getTargetCPU(); - ImageBinary.StringData["triple"] = TripleStr; - ImageBinary.StringData["arch"] = CPURef; - llvm::SmallString<0> Buffer = OffloadBinary::write(ImageBinary); - if (Buffer.size() % OffloadBinary::getAlignment() != 0) - // Offload binary has invalid size alignment - return false; - OS2 << Buffer; - if (Error E = writeFile(HostOutPath, - StringRef(BinaryData.begin(), BinaryData.size()))) - return false; - return true; -} - -extern "C" bool LLVMRustOffloadEmbedBufferInModule(LLVMModuleRef HostM, - const char *HostOutPath) { - auto MBOrErr = MemoryBuffer::getFile(HostOutPath); - if (!MBOrErr) { - auto E = MBOrErr.getError(); - auto _B = errorCodeToError(E); - return false; - } - MemoryBufferRef Buf = (*MBOrErr)->getMemBufferRef(); - Module *M = unwrap(HostM); - StringRef SectionName = ".llvm.offloading"; - Align Alignment = Align(8); - llvm::embedBufferInModule(*M, Buf, SectionName, Alignment); - return true; -} - -// Clone OldFn into NewFn, remapping its arguments to RebuiltArgs. -// Each arg of OldFn is replaced with the corresponding value in RebuiltArgs. -// For scalars, RebuiltArgs contains the value cast and/or truncated to the -// original type. -extern "C" void LLVMRustOffloadMapper(LLVMValueRef OldFn, LLVMValueRef NewFn, - const LLVMValueRef *RebuiltArgs) { - llvm::Function *oldFn = llvm::unwrap(OldFn); - llvm::Function *newFn = llvm::unwrap(NewFn); - - // Map old arguments to new arguments. We skip the first dyn_ptr argument, - // since it can't be used directly by user code. - llvm::ValueToValueMapTy vmap; - auto newArgIt = newFn->arg_begin(); - newArgIt->setName("dyn_ptr"); - - unsigned i = 0; - for (auto &oldArg : oldFn->args()) { - vmap[&oldArg] = unwrap(RebuiltArgs[i++]); - } - - llvm::SmallVector returns; - llvm::CloneFunctionInto(newFn, oldFn, vmap, - llvm::CloneFunctionChangeType::LocalChangesOnly, - returns); - - BasicBlock &entry = newFn->getEntryBlock(); - BasicBlock &clonedEntry = *std::next(newFn->begin()); - - if (entry.getTerminator()) - entry.getTerminator()->eraseFromParent(); - - IRBuilder<> B(&entry); - B.CreateBr(&clonedEntry); -} -#endif - extern "C" LLVMValueRef LLVMRustGetNamedValue(LLVMModuleRef M, const char *Name, size_t NameLen) { return wrap(unwrap(M)->getNamedValue(StringRef(Name, NameLen))); diff --git a/compiler/rustc_llvm/llvm-wrapper/offload/CMakeLists.txt b/compiler/rustc_llvm/llvm-wrapper/offload/CMakeLists.txt new file mode 100644 index 0000000000000..37c747a902d87 --- /dev/null +++ b/compiler/rustc_llvm/llvm-wrapper/offload/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.20) +project(RustOffload LANGUAGES CXX) + +find_package(LLVM CONFIG REQUIRED) + +add_library(RustOffload-${LLVM_VERSION_MAJOR} SHARED + OffloadWrapper.cpp +) + +target_include_directories(RustOffload-${LLVM_VERSION_MAJOR} PRIVATE + ${LLVM_INCLUDE_DIRS} +) + +target_link_libraries(RustOffload-${LLVM_VERSION_MAJOR} PRIVATE + LLVM +) + +if(NOT LLVM_ENABLE_RTTI) + target_compile_options( + RustOffload-${LLVM_VERSION_MAJOR} + PRIVATE -fno-rtti + ) +endif() + +install(TARGETS RustOffload-${LLVM_VERSION_MAJOR} + LIBRARY DESTINATION lib +) diff --git a/compiler/rustc_llvm/llvm-wrapper/offload/OffloadWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/offload/OffloadWrapper.cpp new file mode 100644 index 0000000000000..8c18f2453e9d8 --- /dev/null +++ b/compiler/rustc_llvm/llvm-wrapper/offload/OffloadWrapper.cpp @@ -0,0 +1,117 @@ +#include "../SuppressLLVMWarnings.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Bitcode/BitcodeWriter.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/Object/OffloadBinary.h" +#include "llvm/Support/CBindingWrapping.h" +#include "llvm/Support/FileOutputBuffer.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/Transforms/Utils/Cloning.h" +#include "llvm/Transforms/Utils/ModuleUtils.h" +#include "llvm/Transforms/Utils/ValueMapper.h" + +using namespace llvm; +using namespace llvm::object; + +static Error writeFile(StringRef Filename, StringRef Data) { + Expected> OutputOrErr = + FileOutputBuffer::create(Filename, Data.size()); + if (!OutputOrErr) + return OutputOrErr.takeError(); + std::unique_ptr Output = std::move(*OutputOrErr); + llvm::copy(Data, Output->getBufferStart()); + if (Error E = Output->commit()) + return E; + return Error::success(); +} + +// This is the first of many steps in creating a binary using llvm offload, +// to run code on the gpu. Concrete, it replaces the following binary use: +// clang-offload-packager -o device.bin +// --image=file=device.bc,triple=amdgcn-amd-amdhsa,arch=gfx90a,kind=openmp +// The input module is the rust code compiled for a gpu target like amdgpu. +// Based on clang/tools/clang-offload-packager/ClangOffloadPackager.cpp +extern "C" bool LLVMRustBundleImages(LLVMModuleRef M, TargetMachine &TM, + const char *HostOutPath) { + std::string Storage; + llvm::raw_string_ostream OS1(Storage); + llvm::WriteBitcodeToFile(*unwrap(M), OS1); + OS1.flush(); + auto MB = llvm::MemoryBuffer::getMemBufferCopy(Storage, "device.bc"); + + SmallVector BinaryData; + raw_svector_ostream OS2(BinaryData); + + OffloadBinary::OffloadingImage ImageBinary{}; + ImageBinary.TheImageKind = object::IMG_Bitcode; + ImageBinary.Image = std::move(MB); + ImageBinary.TheOffloadKind = object::OFK_OpenMP; + + std::string TripleStr = TM.getTargetTriple().str(); + llvm::StringRef CPURef = TM.getTargetCPU(); + ImageBinary.StringData["triple"] = TripleStr; + ImageBinary.StringData["arch"] = CPURef; + llvm::SmallString<0> Buffer = OffloadBinary::write(ImageBinary); + if (Buffer.size() % OffloadBinary::getAlignment() != 0) + // Offload binary has invalid size alignment + return false; + OS2 << Buffer; + if (Error E = writeFile(HostOutPath, + StringRef(BinaryData.begin(), BinaryData.size()))) + return false; + return true; +} + +extern "C" bool LLVMRustOffloadEmbedBufferInModule(LLVMModuleRef HostM, + const char *HostOutPath) { + auto MBOrErr = MemoryBuffer::getFile(HostOutPath); + if (!MBOrErr) { + auto E = MBOrErr.getError(); + auto _B = errorCodeToError(E); + return false; + } + MemoryBufferRef Buf = (*MBOrErr)->getMemBufferRef(); + Module *M = unwrap(HostM); + StringRef SectionName = ".llvm.offloading"; + Align Alignment = Align(8); + llvm::embedBufferInModule(*M, Buf, SectionName, Alignment); + return true; +} + +// Clone OldFn into NewFn, remapping its arguments to RebuiltArgs. +// Each arg of OldFn is replaced with the corresponding value in RebuiltArgs. +// For scalars, RebuiltArgs contains the value cast and/or truncated to the +// original type. +extern "C" void LLVMRustOffloadMapper(LLVMValueRef OldFn, LLVMValueRef NewFn, + const LLVMValueRef *RebuiltArgs) { + llvm::Function *oldFn = llvm::unwrap(OldFn); + llvm::Function *newFn = llvm::unwrap(NewFn); + + // Map old arguments to new arguments. We skip the first dyn_ptr argument, + // since it can't be used directly by user code. + llvm::ValueToValueMapTy vmap; + auto newArgIt = newFn->arg_begin(); + newArgIt->setName("dyn_ptr"); + + unsigned i = 0; + for (auto &oldArg : oldFn->args()) { + vmap[&oldArg] = unwrap(RebuiltArgs[i++]); + } + + llvm::SmallVector returns; + llvm::CloneFunctionInto(newFn, oldFn, vmap, + llvm::CloneFunctionChangeType::LocalChangesOnly, + returns); + + BasicBlock &entry = newFn->getEntryBlock(); + BasicBlock &clonedEntry = *std::next(newFn->begin()); + + if (entry.getTerminator()) + entry.getTerminator()->eraseFromParent(); + + IRBuilder<> B(&entry); + B.CreateBr(&clonedEntry); +} diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 021a652a5ac50..25c3df3ae541e 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -2275,10 +2275,18 @@ impl CommandLineStep for Assemble { if builder.config.llvm_offload && !builder.config.dry_run() { debug!("`llvm_offload` requested"); + let rust_offload = builder.ensure(llvm::RustOffload { target: build_compiler.host }); let offload_install = builder.ensure(llvm::OmpOffload { target: build_compiler.host }); if let Some(_llvm_config) = builder.llvm_config(builder.config.host_target) { let target_libdir = builder.sysroot_target_libdir(target_compiler, target_compiler.host); + let rust_offload_dst_lib = target_libdir.join(rust_offload.rust_offload_filename()); + builder.copy_link( + &rust_offload.rust_offload_path(), + &rust_offload_dst_lib, + FileType::NativeLibrary, + ); + for p in offload_install.offload_paths() { let libname = p.file_name().unwrap(); let dst_lib = target_libdir.join(libname); diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index d3276cfb5371b..2e14082310350 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -942,6 +942,96 @@ fn get_var(var_base: &str, host: &str, target: &str) -> Option { .or_else(|| env::var_os(var_base)) } +#[derive(Clone)] +pub struct BuiltRustOffload { + /// Path to the rust offload dylib + offload: PathBuf, +} + +impl BuiltRustOffload { + pub fn rust_offload_path(&self) -> PathBuf { + self.offload.clone() + } + + pub fn rust_offload_filename(&self) -> String { + self.offload.file_name().unwrap().to_str().unwrap().to_owned() + } +} + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct RustOffload { + pub target: TargetSelection, +} + +impl CommandLineStep for RustOffload { + type Output = BuiltRustOffload; + const IS_HOST: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.alias("rust-offload") + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(RustOffload { target: run.target }); + } + + fn run(self, builder: &Builder<'_>) -> Self::Output { + if builder.config.dry_run() { + return BuiltRustOffload { + offload: builder.config.tempdir().join("rust-offload-dry-run"), + }; + } + + let target = self.target; + + let LlvmResult { host_llvm_config, llvm_cmake_dir } = builder.ensure(Llvm { target }); + + let out_dir = builder.rust_offload_out(target); + + let llvm_version_major = llvm::get_llvm_version_major(builder, &host_llvm_config); + let lib_ext = std::env::consts::DLL_EXTENSION; + let lib_rust_offload = format!("libRustOffload-{llvm_version_major}"); + let build_dir = out_dir.join(libdir(target)); + let dylib = build_dir.join(&lib_rust_offload).with_extension(lib_ext); + + let mut cfg = + cmake::Config::new(builder.src.join("compiler/rustc_llvm/llvm-wrapper/offload/")); + + // Logic copied from `configure_llvm` + // ThinLTO is only available when building with LLVM, enabling LLD is required. + // Apple's linker ld64 supports ThinLTO out of the box though, so don't use LLD on Darwin. + let mut ldflags = LdFlags::default(); + if builder.config.llvm_thin_lto && !target.contains("apple") { + ldflags.push_all("-fuse-ld=lld"); + } + + configure_cmake(builder, target, &mut cfg, true, ldflags, CcFlags::default(), &[]); + + let profile = match (builder.config.llvm_optimize, builder.config.llvm_release_debuginfo) { + (false, _) => "Debug", + (true, false) => "Release", + (true, true) => "RelWithDebInfo", + }; + + cfg.out_dir(&out_dir) + .profile(profile) + .env("LLVM_CONFIG_REAL", &host_llvm_config) + .define("LLVM_DIR", llvm_cmake_dir); + + cfg.build(); + + if !dylib.exists() { + eprintln!( + "`{lib_rust_offload}` not found in `{}`. Either the build has failed or RustOffload was built with a wrong version of LLVM", + build_dir.display() + ); + exit!(1); + } + + BuiltRustOffload { offload: dylib } + } +} + #[derive(Clone)] pub struct BuiltOmpOffload { /// Path to the omp and offload dylibs. @@ -998,7 +1088,7 @@ impl CommandLineStep for OmpOffload { // Running cmake twice in the same folder is known to cause issues, like deleting existing // binaries. We therefore write our offload artifacts into it's own folder, instead of // using the llvm build dir. - let out_dir = builder.offload_out(target); + let out_dir = builder.omp_offload_out(target); let mut files = vec![]; let lib_ext = std::env::consts::DLL_EXTENSION; diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 051e01a0a6666..22adbfd946965 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -823,6 +823,7 @@ impl<'a> Builder<'a> { tool::CargoMiri, llvm::Lld, llvm::Enzyme, + llvm::RustOffload, llvm::CrtBeginEnd, tool::RustdocGUITest, tool::OptimizedDist, diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 7d119247b3bac..27a03b1616192 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -983,10 +983,14 @@ impl Build { self.out.join(&*target.triple).join("enzyme") } - fn offload_out(&self, target: TargetSelection) -> PathBuf { + fn omp_offload_out(&self, target: TargetSelection) -> PathBuf { self.out.join(&*target.triple).join("offload") } + fn rust_offload_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(&*target.triple).join("rust-offload") + } + fn lld_out(&self, target: TargetSelection) -> PathBuf { self.out.join(target).join("lld") } From 93a46b6e969575da44d3900d9d6e906f2254305f Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 7 Aug 2026 09:08:24 +1000 Subject: [PATCH 48/57] Improve the canonical param env cache Currently it is modified with the very clunky `canonical_param_env_cache_get_or_insert` method, which takes two closures. This commit replaces that with `with_canonical_param_env_cache` a simpler accessor that is very similar to the nearby `with_global_cache`. This lets `canonicalize_param_env` use normal hash map operations. The commit also: - Introduces a dedicated `CanonicalParamEnvCache` newtype. - Adds a helpful comment to `CanonicalizeParamEnvCacheEntry::param_env`. --- compiler/rustc_middle/src/ty/context.rs | 3 +- .../src/ty/context/impl_interner.rs | 10 ++---- .../src/canonical/canonicalizer.rs | 34 +++++++++---------- compiler/rustc_type_ir/src/canonical.rs | 7 ++++ compiler/rustc_type_ir/src/interner.rs | 10 +++--- 5 files changed, 31 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index f834784f98847..30ee1d945dc18 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -749,8 +749,7 @@ pub struct GlobalCtxt<'tcx> { /// Caches the results of goal evaluation in the new solver. pub new_solver_evaluation_cache: Lock>>, - pub new_solver_canonical_param_env_cache: - Lock, ty::CanonicalParamEnvCacheEntry>>>, + pub new_solver_canonical_param_env_cache: Lock>>, pub canonical_param_env_cache: CanonicalParamEnvCache<'tcx>, diff --git a/compiler/rustc_middle/src/ty/context/impl_interner.rs b/compiler/rustc_middle/src/ty/context/impl_interner.rs index 470abf327679f..ecc8d8867a279 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -146,15 +146,11 @@ impl<'tcx> Interner for TyCtxt<'tcx> { f(&mut *self.new_solver_evaluation_cache.lock()) } - fn canonical_param_env_cache_get_or_insert( + fn with_canonical_param_env_cache( self, - param_env: ty::ParamEnv<'tcx>, - f: impl FnOnce() -> ty::CanonicalParamEnvCacheEntry, - from_entry: impl FnOnce(&ty::CanonicalParamEnvCacheEntry) -> R, + f: impl FnOnce(&mut ty::CanonicalParamEnvCache) -> R, ) -> R { - let mut cache = self.new_solver_canonical_param_env_cache.lock(); - let entry = cache.entry(param_env).or_insert_with(f); - from_entry(entry) + f(&mut *self.new_solver_canonical_param_env_cache.lock()) } fn assert_evaluation_is_concurrent(&self) { diff --git a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs index 20402649ceabd..385741a6f1b3d 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs @@ -132,9 +132,8 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { // globally cached. We don't rely on any additional information when canonicalizing // placeholders. if !param_env.has_non_region_infer() { - delegate.cx().canonical_param_env_cache_get_or_insert( - param_env, - || { + delegate.cx().with_canonical_param_env_cache(|cache| { + let entry = cache.0.entry(param_env).or_insert_with(|| { let mut env_canonicalizer = Canonicalizer { delegate, canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), @@ -154,21 +153,20 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { var_kinds: env_canonicalizer.var_kinds, variables: env_canonicalizer.variables, } - }, - |&CanonicalParamEnvCacheEntry { - param_env, - variables: ref cache_variables, - ref variable_lookup_table, - ref var_kinds, - }| { - // FIXME(nnethercote): for reasons I don't understand, this `new`+`extend` - // combination is faster than `variables.clone()`, because it somehow avoids - // some allocations. - let mut variables = ThinVec::new(); - variables.extend(cache_variables.iter().copied()); - (param_env, variables, var_kinds.clone(), variable_lookup_table.clone()) - }, - ) + }); + + // FIXME(nnethercote): for reasons I don't understand, this `new`+`extend` + // combination is faster than `variables.clone()`, because it somehow avoids + // some allocations. + let mut variables = ThinVec::new(); + variables.extend(entry.variables.iter().copied()); + ( + entry.param_env, + variables, + entry.var_kinds.clone(), + entry.variable_lookup_table.clone(), + ) + }) } else { let mut env_canonicalizer = Canonicalizer { delegate, diff --git a/compiler/rustc_type_ir/src/canonical.rs b/compiler/rustc_type_ir/src/canonical.rs index e0cbc0890b761..e76d6b86d5e5c 100644 --- a/compiler/rustc_type_ir/src/canonical.rs +++ b/compiler/rustc_type_ir/src/canonical.rs @@ -364,8 +364,15 @@ impl Index for CanonicalVarValues { } } +#[derive_where(Default; I: Interner)] +pub struct CanonicalParamEnvCache( + pub HashMap>, +); + #[derive_where(Clone, Debug; I: Interner)] pub struct CanonicalParamEnvCacheEntry { + // Note: this `param_env` is the canonicalized form of the key for this entry in the enclosing + // `CanonicalParamEnvCache`. pub param_env: I::ParamEnv, pub variables: ThinVec, pub variable_lookup_table: HashMap, diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 9ca1ec19a1339..56a911bdb4b8b 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -18,8 +18,8 @@ use crate::solve::{ }; use crate::visit::{Flags, TypeVisitable}; use crate::{ - self as ty, BoundRegion, BoundVar, CanonicalParamEnvCacheEntry, DebruijnIndex, Region, - RegionKind, TraitRef, search_graph, + self as ty, BoundRegion, BoundVar, CanonicalParamEnvCache, DebruijnIndex, Region, RegionKind, + TraitRef, search_graph, }; #[cfg_attr(feature = "nightly", rustc_diagnostic_item = "type_ir_interner")] @@ -204,11 +204,9 @@ pub trait Interner: fn with_global_cache(self, f: impl FnOnce(&mut search_graph::GlobalCache) -> R) -> R; - fn canonical_param_env_cache_get_or_insert( + fn with_canonical_param_env_cache( self, - param_env: Self::ParamEnv, - f: impl FnOnce() -> CanonicalParamEnvCacheEntry, - from_entry: impl FnOnce(&CanonicalParamEnvCacheEntry) -> R, + f: impl FnOnce(&mut CanonicalParamEnvCache) -> R, ) -> R; /// Useful for testing. If a cache entry is replaced, this should From 8ba2b46c78bfc18068ee07393ba2a5e4955aabbc Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 7 Aug 2026 09:15:04 +1000 Subject: [PATCH 49/57] Update a comment I now understand what is happening here. --- .../rustc_next_trait_solver/src/canonical/canonicalizer.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs index 385741a6f1b3d..78d8a0dbba708 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs @@ -155,9 +155,9 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { } }); - // FIXME(nnethercote): for reasons I don't understand, this `new`+`extend` - // combination is faster than `variables.clone()`, because it somehow avoids - // some allocations. + // The obvious thing to do here is `variables.clone()`. But this `new`+`extend` + // combination results in the variables having more spare capacity, which avoids + // some later allocations and makes things a little faster. let mut variables = ThinVec::new(); variables.extend(entry.variables.iter().copied()); ( From 87d95f53a79e89cb669800db46f982956b40b26f Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 7 Aug 2026 10:01:50 +1000 Subject: [PATCH 50/57] Introduce `Canonicalizer::new` It avoids some repetition. --- .../src/canonical/canonicalizer.rs | 70 ++++++++----------- 1 file changed, 29 insertions(+), 41 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs index 78d8a0dbba708..1ebbeaec482e2 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs @@ -83,23 +83,25 @@ pub(super) struct Canonicalizer<'a, D: SolverDelegate, I: Interner } impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { - pub(super) fn canonicalize_response>( - delegate: &'a D, - max_input_universe: ty::UniverseIndex, - value: T, - ) -> ty::Canonical { - let mut canonicalizer = Canonicalizer { + fn new(delegate: &'a D, canonicalize_mode: CanonicalizeMode) -> Self { + Canonicalizer { delegate, - canonicalize_mode: CanonicalizeMode::Response { max_input_universe }, - + canonicalize_mode, variables: Default::default(), variable_lookup_table: Default::default(), sub_root_lookup_table: Default::default(), var_kinds: Default::default(), - cache: Default::default(), - }; + } + } + pub(super) fn canonicalize_response>( + delegate: &'a D, + max_input_universe: ty::UniverseIndex, + value: T, + ) -> ty::Canonical { + let mut canonicalizer = + Canonicalizer::new(delegate, CanonicalizeMode::Response { max_input_universe }); let value = if value.has_type_flags(NEEDS_CANONICAL) { value.fold_with(&mut canonicalizer) } else { @@ -134,17 +136,10 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { if !param_env.has_non_region_infer() { delegate.cx().with_canonical_param_env_cache(|cache| { let entry = cache.0.entry(param_env).or_insert_with(|| { - let mut env_canonicalizer = Canonicalizer { + let mut env_canonicalizer = Canonicalizer::new( delegate, - canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), - - variables: Default::default(), - variable_lookup_table: Default::default(), - sub_root_lookup_table: Default::default(), - var_kinds: Default::default(), - - cache: Default::default(), - }; + CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), + ); let param_env = param_env.fold_with(&mut env_canonicalizer); debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty()); CanonicalParamEnvCacheEntry { @@ -168,17 +163,10 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { ) }) } else { - let mut env_canonicalizer = Canonicalizer { + let mut env_canonicalizer = Canonicalizer::new( delegate, - canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), - - variables: Default::default(), - variable_lookup_table: Default::default(), - sub_root_lookup_table: Default::default(), - var_kinds: Default::default(), - - cache: Default::default(), - }; + CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), + ); let param_env = param_env.fold_with(&mut env_canonicalizer); debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty()); ( @@ -205,23 +193,23 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { // First canonicalize the `param_env` while keeping `'static` let (param_env, variables, var_kinds, variable_lookup_table) = Canonicalizer::canonicalize_param_env(delegate, input.goal.param_env); + // Then canonicalize the rest of the input without keeping `'static` // while *mostly* reusing the canonicalizer from above. + // + // We do not reuse the cache as it may contain entries whose canonicalized + // value contains `'static`. While we could alternatively handle this by + // checking for `'static` when using cached entries, this does not + // feel worth the effort. I do not expect that a `ParamEnv` will ever + // contain large enough types for caching to be necessary. let mut rest_canonicalizer = Canonicalizer { - delegate, - canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::Predicate), - variables, variable_lookup_table, - sub_root_lookup_table: Default::default(), var_kinds, - - // We do not reuse the cache as it may contain entries whose canonicalized - // value contains `'static`. While we could alternatively handle this by - // checking for `'static` when using cached entries, this does not - // feel worth the effort. I do not expect that a `ParamEnv` will ever - // contain large enough types for caching to be necessary. - cache: Default::default(), + ..Canonicalizer::new( + delegate, + CanonicalizeMode::Input(CanonicalizeInputKind::Predicate), + ) }; let predicate = input.goal.predicate; From 8b5cad12dd5b0b8ac225811a78d5319fe6eeb198 Mon Sep 17 00:00:00 2001 From: Senthilnathan Date: Fri, 7 Aug 2026 10:42:36 +0530 Subject: [PATCH 51/57] Add note to invalidate iterator when mutating inside a for-loop --- .../src/diagnostics/conflict_errors.rs | 92 +++++++++++++++++++ .../vec-mut-iter-borrow.stderr | 19 ++-- .../borrowck-for-loop-head-linkage.stderr | 44 +++++---- tests/ui/borrowck/issue-82462.stderr | 25 +++-- .../ui/borrowck/mutate-vec-while-iterating.rs | 21 +++++ .../mutate-vec-while-iterating.stderr | 40 ++++++++ ...ng-updating-cursor-issue-108704.nll.stderr | 19 +++- ...dating-cursor-issue-108704.polonius.stderr | 19 +++- tests/ui/suggestions/issue-102972.stderr | 40 ++++---- 9 files changed, 260 insertions(+), 59 deletions(-) create mode 100644 tests/ui/borrowck/mutate-vec-while-iterating.rs create mode 100644 tests/ui/borrowck/mutate-vec-while-iterating.stderr diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index e3e36f9bbc715..d716c2d57039e 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -1880,6 +1880,14 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> { issued_borrow.borrowed_place, &issued_spans, ); + self.explain_iterator_invalidation_in_for_loop_if_applicable( + &mut err, + &issued_spans, + place, + issued_borrow.borrowed_place, + issued_borrow.kind, + span, + ); err } @@ -1903,6 +1911,14 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> { span, issued_span, ); + self.explain_iterator_invalidation_in_for_loop_if_applicable( + &mut err, + &issued_spans, + place, + issued_borrow.borrowed_place, + issued_borrow.kind, + span, + ); self.suggest_using_closure_argument_instead_of_capture( &mut err, issued_borrow.borrowed_place, @@ -2618,6 +2634,50 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> { } } + /// Explain iterator invalidation when mutating a collection in a for loop. + /// + /// For example: + /// ```compile_fail + /// let mut values = vec![1, 2, 3]; + /// for value in &values { + /// values.push(4); + /// } + /// ``` + fn explain_iterator_invalidation_in_for_loop_if_applicable( + &self, + err: &mut Diag<'_>, + issued_spans: &UseSpans<'tcx>, + place: Place<'tcx>, + borrowed_place: Place<'tcx>, + borrow_kind: BorrowKind, + gen_span: Span, + ) { + let issue_span = issued_spans.args_or_use(); + let tcx = self.infcx.tcx; + + let Some(body_id) = tcx.hir_node(self.mir_hir_id()).body_id() else { return }; + + if let Some(for_span) = find_for_loop_span(tcx, body_id, issue_span) + && place.local == borrowed_place.local + && for_span.contains(gen_span) + { + let place_desc = self.describe_any_place(place.as_ref()); + let borrow_kind_str = + if matches!(borrow_kind, BorrowKind::Mut { .. }) { "mutably" } else { "immutably" }; + err.span_label( + for_span, + format!( + "this for loop borrows {place_desc} {borrow_kind_str}, \ + preventing mutation within its body" + ), + ); + err.help( + "consider using an index-based loop instead, or collecting \ + modifications into a separate collection", + ); + } + } + /// Suggest using closure argument instead of capture. /// /// For example: @@ -4654,6 +4714,38 @@ enum AnnotatedBorrowFnSignature<'tcx> { }, } +/// Find the `Match` expression desugared from a for loop, whose +/// `IntoIter::into_iter` argument contains `issue_span`. +/// Returns the for-loop match expression span. +fn find_for_loop_span<'hir>( + tcx: TyCtxt<'hir>, + body_id: hir::BodyId, + issue_span: Span, +) -> Option { + struct ExprFinder<'hir> { + tcx: TyCtxt<'hir>, + issue_span: Span, + result: Option, + } + impl<'hir> Visitor<'hir> for ExprFinder<'hir> { + fn visit_expr(&mut self, ex: &'hir hir::Expr<'hir>) { + if let hir::ExprKind::Match(scrutinee, _, hir::MatchSource::ForLoopDesugar) = ex.kind + && let hir::ExprKind::Call(path, [arg]) = scrutinee.kind + && let hir::ExprKind::Path(qpath) = path.kind + && self.tcx.qpath_is_lang_item(qpath, LangItem::IntoIterIntoIter) + && arg.span.contains(self.issue_span) + { + self.result = Some(ex.span); + return; + } + hir::intravisit::walk_expr(self, ex); + } + } + let mut finder = ExprFinder { tcx, issue_span, result: None }; + finder.visit_expr(tcx.hir_body(body_id).value); + finder.result +} + impl<'tcx> AnnotatedBorrowFnSignature<'tcx> { /// Annotate the provided diagnostic with information about borrow from the fn signature that /// helps explain. diff --git a/tests/ui/array-slice-vec/vec-mut-iter-borrow.stderr b/tests/ui/array-slice-vec/vec-mut-iter-borrow.stderr index d9343140fb1dc..7d32670effe3f 100644 --- a/tests/ui/array-slice-vec/vec-mut-iter-borrow.stderr +++ b/tests/ui/array-slice-vec/vec-mut-iter-borrow.stderr @@ -1,13 +1,18 @@ error[E0499]: cannot borrow `xs` as mutable more than once at a time --> $DIR/vec-mut-iter-borrow.rs:5:9 | -LL | for x in &mut xs { - | ------- - | | - | first mutable borrow occurs here - | first borrow later used here -LL | xs.push(1) - | ^^ second mutable borrow occurs here +LL | for x in &mut xs { + | - ------- + | | | + | | first mutable borrow occurs here + | _____| first borrow later used here + | | +LL | | xs.push(1) + | | ^^ second mutable borrow occurs here +LL | | } + | |_____- this for loop borrows `xs` mutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection error: aborting due to 1 previous error diff --git a/tests/ui/borrowck/borrowck-for-loop-head-linkage.stderr b/tests/ui/borrowck/borrowck-for-loop-head-linkage.stderr index f47dce453696e..8cdb2a0f8878d 100644 --- a/tests/ui/borrowck/borrowck-for-loop-head-linkage.stderr +++ b/tests/ui/borrowck/borrowck-for-loop-head-linkage.stderr @@ -1,26 +1,38 @@ error[E0502]: cannot borrow `vector` as mutable because it is also borrowed as immutable --> $DIR/borrowck-for-loop-head-linkage.rs:7:9 | -LL | for &x in &vector { - | ------- - | | - | immutable borrow occurs here - | immutable borrow later used here -LL | let cap = vector.capacity(); -LL | vector.extend(repeat(0)); - | ^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here +LL | for &x in &vector { + | - ------- + | | | + | | immutable borrow occurs here + | _____| immutable borrow later used here + | | +LL | | let cap = vector.capacity(); +LL | | vector.extend(repeat(0)); + | | ^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here +LL | | vector[1] = 5; +LL | | } + | |_____- this for loop borrows `vector` immutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection error[E0502]: cannot borrow `vector` as mutable because it is also borrowed as immutable --> $DIR/borrowck-for-loop-head-linkage.rs:8:9 | -LL | for &x in &vector { - | ------- - | | - | immutable borrow occurs here - | immutable borrow later used here -... -LL | vector[1] = 5; - | ^^^^^^ mutable borrow occurs here +LL | for &x in &vector { + | - ------- + | | | + | | immutable borrow occurs here + | _____| immutable borrow later used here + | | +LL | | let cap = vector.capacity(); +LL | | vector.extend(repeat(0)); +LL | | vector[1] = 5; + | | ^^^^^^ mutable borrow occurs here +LL | | } + | |_____- this for loop borrows `vector` immutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection error: aborting due to 2 previous errors diff --git a/tests/ui/borrowck/issue-82462.stderr b/tests/ui/borrowck/issue-82462.stderr index 8cb4583eba940..ed5a4cc2d219f 100644 --- a/tests/ui/borrowck/issue-82462.stderr +++ b/tests/ui/borrowck/issue-82462.stderr @@ -1,17 +1,22 @@ error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable --> $DIR/issue-82462.rs:18:9 | -LL | for x in DroppingSlice(&*v).iter() { - | ------------------ - | | | - | | immutable borrow occurs here - | a temporary with access to the immutable borrow is created here ... -LL | v.push(*x); - | ^^^^^^^^^^ mutable borrow occurs here -LL | break; -LL | } - | - ... and the immutable borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `DroppingSlice` +LL | for x in DroppingSlice(&*v).iter() { + | - ------------------ + | | | | + | | | immutable borrow occurs here + | _____| a temporary with access to the immutable borrow is created here ... + | | +LL | | v.push(*x); + | | ^^^^^^^^^^ mutable borrow occurs here +LL | | break; +LL | | } + | | - + | | | + | |_____... and the immutable borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `DroppingSlice` + | this for loop borrows `v` immutably, preventing mutation within its body | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped | LL | }; diff --git a/tests/ui/borrowck/mutate-vec-while-iterating.rs b/tests/ui/borrowck/mutate-vec-while-iterating.rs new file mode 100644 index 0000000000000..18cc906ee500b --- /dev/null +++ b/tests/ui/borrowck/mutate-vec-while-iterating.rs @@ -0,0 +1,21 @@ +// Regression test for https://github.com/rust-lang/rust/issues/159489 + +fn main() { + let mut values = vec![1, 2, 3]; + + for value in &values { + if *value == 2 { + values.push(4); //~ ERROR E0502 + } + } +} + +fn mutate_while_iterating_mut() { + let mut values = vec![1, 2, 3]; + + for value in &mut values { + if *value == 2 { + values.push(4); //~ ERROR E0499 + } + } +} diff --git a/tests/ui/borrowck/mutate-vec-while-iterating.stderr b/tests/ui/borrowck/mutate-vec-while-iterating.stderr new file mode 100644 index 0000000000000..01f42e9689ab7 --- /dev/null +++ b/tests/ui/borrowck/mutate-vec-while-iterating.stderr @@ -0,0 +1,40 @@ +error[E0502]: cannot borrow `values` as mutable because it is also borrowed as immutable + --> $DIR/mutate-vec-while-iterating.rs:8:13 + | +LL | for value in &values { + | - ------- + | | | + | | immutable borrow occurs here + | _____| immutable borrow later used here + | | +LL | | if *value == 2 { +LL | | values.push(4); + | | ^^^^^^^^^^^^^^ mutable borrow occurs here +LL | | } +LL | | } + | |_____- this for loop borrows `values` immutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection + +error[E0499]: cannot borrow `values` as mutable more than once at a time + --> $DIR/mutate-vec-while-iterating.rs:18:13 + | +LL | for value in &mut values { + | - ----------- + | | | + | | first mutable borrow occurs here + | _____| first borrow later used here + | | +LL | | if *value == 2 { +LL | | values.push(4); + | | ^^^^^^ second mutable borrow occurs here +LL | | } +LL | | } + | |_____- this for loop borrows `values` mutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0499, E0502. +For more information about an error, try `rustc --explain E0499`. diff --git a/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.nll.stderr b/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.nll.stderr index b768f60590cb0..59e5c0502cc0e 100644 --- a/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.nll.stderr +++ b/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.nll.stderr @@ -1,11 +1,20 @@ error[E0499]: cannot borrow `*elements` as mutable more than once at a time --> $DIR/iterating-updating-cursor-issue-108704.rs:41:26 | -LL | for (idx, el) in elements.iter_mut().enumerate() { - | ^^^^^^^^ - | | - | `*elements` was mutably borrowed here in the previous iteration of the loop - | first borrow used here, in later iteration of loop +LL | for (idx, el) in elements.iter_mut().enumerate() { + | - ^^^^^^^^ + | | | + | | `*elements` was mutably borrowed here in the previous iteration of the loop + | _________| first borrow used here, in later iteration of loop + | | +LL | | if el.name == *p { +LL | | elements = &mut el.children; +LL | | break; +LL | | } +LL | | } + | |_________- this for loop borrows `*elements` mutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection error: aborting due to 1 previous error diff --git a/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.polonius.stderr b/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.polonius.stderr index b768f60590cb0..59e5c0502cc0e 100644 --- a/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.polonius.stderr +++ b/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.polonius.stderr @@ -1,11 +1,20 @@ error[E0499]: cannot borrow `*elements` as mutable more than once at a time --> $DIR/iterating-updating-cursor-issue-108704.rs:41:26 | -LL | for (idx, el) in elements.iter_mut().enumerate() { - | ^^^^^^^^ - | | - | `*elements` was mutably borrowed here in the previous iteration of the loop - | first borrow used here, in later iteration of loop +LL | for (idx, el) in elements.iter_mut().enumerate() { + | - ^^^^^^^^ + | | | + | | `*elements` was mutably borrowed here in the previous iteration of the loop + | _________| first borrow used here, in later iteration of loop + | | +LL | | if el.name == *p { +LL | | elements = &mut el.children; +LL | | break; +LL | | } +LL | | } + | |_________- this for loop borrows `*elements` mutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection error: aborting due to 1 previous error diff --git a/tests/ui/suggestions/issue-102972.stderr b/tests/ui/suggestions/issue-102972.stderr index 438f28ad03264..1ff972fea0610 100644 --- a/tests/ui/suggestions/issue-102972.stderr +++ b/tests/ui/suggestions/issue-102972.stderr @@ -1,14 +1,18 @@ error[E0499]: cannot borrow `chars` as mutable more than once at a time --> $DIR/issue-102972.rs:6:9 | -LL | for _c in chars.by_ref() { - | -------------- - | | - | first mutable borrow occurs here - | first borrow later used here -LL | chars.next(); - | ^^^^^ second mutable borrow occurs here - | +LL | for _c in chars.by_ref() { + | - -------------- + | | | + | | first mutable borrow occurs here + | _____| first borrow later used here + | | +LL | | chars.next(); + | | ^^^^^ second mutable borrow occurs here +LL | | } + | |_____- this for loop borrows `chars` mutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection = note: a for loop advances the iterator for you, the result is stored in `_c` help: if you want to call `next` on a iterator within the loop, consider using `while let` | @@ -39,14 +43,18 @@ LL + while let Some(_i) = iter.next() { error[E0499]: cannot borrow `i` as mutable more than once at a time --> $DIR/issue-102972.rs:22:9 | -LL | for () in i.by_ref() { - | ---------- - | | - | first mutable borrow occurs here - | first borrow later used here -LL | i.next(); - | ^ second mutable borrow occurs here - | +LL | for () in i.by_ref() { + | - ---------- + | | | + | | first mutable borrow occurs here + | _____| first borrow later used here + | | +LL | | i.next(); + | | ^ second mutable borrow occurs here +LL | | } + | |_____- this for loop borrows `i` mutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection = note: a for loop advances the iterator for you, the result is stored in its pattern help: if you want to call `next` on a iterator within the loop, consider using `while let` | From 5c0a7328be12489048b7cff4092b94dbec93b768 Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:48:17 +0330 Subject: [PATCH 52/57] Add regression test for unknown feature name with other errors present --- .../unknown-feature-with-other-errors-58390.rs | 16 ++++++++++++++++ ...nown-feature-with-other-errors-58390.stderr | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/ui/feature-gates/unknown-feature-with-other-errors-58390.rs create mode 100644 tests/ui/feature-gates/unknown-feature-with-other-errors-58390.stderr diff --git a/tests/ui/feature-gates/unknown-feature-with-other-errors-58390.rs b/tests/ui/feature-gates/unknown-feature-with-other-errors-58390.rs new file mode 100644 index 0000000000000..f14c0937360f4 --- /dev/null +++ b/tests/ui/feature-gates/unknown-feature-with-other-errors-58390.rs @@ -0,0 +1,16 @@ +//! Regression test for . +//! +//! An unknown `#![feature(..)]` name used to be silently ignored whenever the crate had any +//! other error, because the check only ran during stability checking. Both errors must be +//! reported. + +#![feature(this_feature_does_not_exist)] //~ ERROR unknown feature `this_feature_does_not_exist` + +struct Foo; + +trait Bar {} + +impl Bar for Foo {} +impl Bar for Foo {} //~ ERROR conflicting implementations of trait `Bar` for type `Foo` + +fn main() {} diff --git a/tests/ui/feature-gates/unknown-feature-with-other-errors-58390.stderr b/tests/ui/feature-gates/unknown-feature-with-other-errors-58390.stderr new file mode 100644 index 0000000000000..4560bd48f58ce --- /dev/null +++ b/tests/ui/feature-gates/unknown-feature-with-other-errors-58390.stderr @@ -0,0 +1,18 @@ +error[E0635]: unknown feature `this_feature_does_not_exist` + --> $DIR/unknown-feature-with-other-errors-58390.rs:7:12 + | +LL | #![feature(this_feature_does_not_exist)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0119]: conflicting implementations of trait `Bar` for type `Foo` + --> $DIR/unknown-feature-with-other-errors-58390.rs:14:1 + | +LL | impl Bar for Foo {} + | ---------------- first implementation here +LL | impl Bar for Foo {} + | ^^^^^^^^^^^^^^^^ conflicting implementation for `Foo` + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0119, E0635. +For more information about an error, try `rustc --explain E0119`. From 2a773d215e9785c877e81cfbeee581056f1e0f12 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 6 Aug 2026 20:10:35 +0200 Subject: [PATCH 53/57] make test use minicore --- .../asm/naked-functions/unused.aarch64.stderr | 22 +++++++++---------- tests/ui/asm/naked-functions/unused.rs | 19 +++++++++++----- .../asm/naked-functions/unused.x86_64.stderr | 22 +++++++++---------- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/tests/ui/asm/naked-functions/unused.aarch64.stderr b/tests/ui/asm/naked-functions/unused.aarch64.stderr index bfb2923b0b8d6..366d338d15b48 100644 --- a/tests/ui/asm/naked-functions/unused.aarch64.stderr +++ b/tests/ui/asm/naked-functions/unused.aarch64.stderr @@ -1,66 +1,66 @@ error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:16:32 + --> $DIR/unused.rs:23:32 | LL | pub extern "C" fn function(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: the lint level is defined here - --> $DIR/naked-functions-unused.rs:5:9 + --> $DIR/unused.rs:11:9 | LL | #![deny(unused)] | ^^^^^^ = note: `#[deny(unused_variables)]` implied by `#[deny(unused)]` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:16:42 + --> $DIR/unused.rs:23:42 | LL | pub extern "C" fn function(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:27:38 + --> $DIR/unused.rs:34:38 | LL | pub extern "C" fn associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:27:48 + --> $DIR/unused.rs:34:48 | LL | pub extern "C" fn associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:35:41 + --> $DIR/unused.rs:42:41 | LL | pub extern "C" fn method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:35:51 + --> $DIR/unused.rs:42:51 | LL | pub extern "C" fn method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:45:40 + --> $DIR/unused.rs:52:40 | LL | extern "C" fn trait_associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:45:50 + --> $DIR/unused.rs:52:50 | LL | extern "C" fn trait_associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:53:43 + --> $DIR/unused.rs:60:43 | LL | extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:53:53 + --> $DIR/unused.rs:60:53 | LL | extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` diff --git a/tests/ui/asm/naked-functions/unused.rs b/tests/ui/asm/naked-functions/unused.rs index 945ab1a40ad0c..51e3e90c1a72e 100644 --- a/tests/ui/asm/naked-functions/unused.rs +++ b/tests/ui/asm/naked-functions/unused.rs @@ -1,9 +1,16 @@ +//@ add-minicore //@ revisions: x86_64 aarch64 -//@ needs-asm-support -//@[x86_64] only-x86_64 -//@[aarch64] only-aarch64 -#![deny(unused)] +//@[x86_64] compile-flags: --target x86_64-unknown-linux-gnu +//@[x86_64] needs-llvm-components: x86 +//@[aarch64] compile-flags: --target aarch64-unknown-linux-gnu +//@[aarch64] needs-llvm-components: aarch64 +//@ ignore-backends: gcc #![crate_type = "lib"] +#![feature(no_core)] +#![no_core] +#![deny(unused)] + +extern crate minicore; pub trait Trait { extern "C" fn trait_associated(a: usize, b: usize) -> usize; @@ -11,7 +18,7 @@ pub trait Trait { } pub mod normal { - use std::arch::asm; + use minicore::asm; pub extern "C" fn function(a: usize, b: usize) -> usize { //~^ ERROR unused variable: `a` @@ -61,7 +68,7 @@ pub mod normal { } pub mod naked { - use std::arch::naked_asm; + use minicore::naked_asm; #[unsafe(naked)] pub extern "C" fn function(a: usize, b: usize) -> usize { diff --git a/tests/ui/asm/naked-functions/unused.x86_64.stderr b/tests/ui/asm/naked-functions/unused.x86_64.stderr index a41e80fdc50d6..366d338d15b48 100644 --- a/tests/ui/asm/naked-functions/unused.x86_64.stderr +++ b/tests/ui/asm/naked-functions/unused.x86_64.stderr @@ -1,66 +1,66 @@ error: unused variable: `a` - --> $DIR/unused.rs:16:32 + --> $DIR/unused.rs:23:32 | LL | pub extern "C" fn function(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: the lint level is defined here - --> $DIR/unused.rs:5:9 + --> $DIR/unused.rs:11:9 | LL | #![deny(unused)] | ^^^^^^ = note: `#[deny(unused_variables)]` implied by `#[deny(unused)]` error: unused variable: `b` - --> $DIR/unused.rs:16:42 + --> $DIR/unused.rs:23:42 | LL | pub extern "C" fn function(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/unused.rs:27:38 + --> $DIR/unused.rs:34:38 | LL | pub extern "C" fn associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/unused.rs:27:48 + --> $DIR/unused.rs:34:48 | LL | pub extern "C" fn associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/unused.rs:35:41 + --> $DIR/unused.rs:42:41 | LL | pub extern "C" fn method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/unused.rs:35:51 + --> $DIR/unused.rs:42:51 | LL | pub extern "C" fn method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/unused.rs:45:40 + --> $DIR/unused.rs:52:40 | LL | extern "C" fn trait_associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/unused.rs:45:50 + --> $DIR/unused.rs:52:50 | LL | extern "C" fn trait_associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/unused.rs:53:43 + --> $DIR/unused.rs:60:43 | LL | extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/unused.rs:53:53 + --> $DIR/unused.rs:60:53 | LL | extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` From 10dbc1c24924e6e7042cabfdce7f215b3bad9442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 7 Aug 2026 13:54:58 +0200 Subject: [PATCH 54/57] Add branch config for perf. unrolling in bors --- .github/workflows/ci.yml | 5 +++-- src/ci/citool/src/main.rs | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f918e3883d6a..0b0c190a533ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,7 @@ on: branches: - automation/bors/auto - automation/bors/try + - automation/bors/try-perf - try-perf pull_request: branches: @@ -34,7 +35,7 @@ concurrency: # We add an exception for try builds (automation/bors/try branch) and unrolled rollup builds # (try-perf), which are all triggered on the same branch, but which should be able to run # concurrently. - group: ${{ github.workflow }}-${{ ((github.ref == 'refs/heads/try-perf' || github.ref == 'refs/heads/automation/bors/try') && github.sha) || github.ref }} + group: ${{ github.workflow }}-${{ ((github.ref == 'refs/heads/try-perf' || github.ref == 'refs/heads/automation/bors/try-perf' || github.ref == 'refs/heads/automation/bors/try') && github.sha) || github.ref }} cancel-in-progress: true env: TOOLSTATE_REPO: "https://github.com/rust-lang-nursery/rust-toolstate" @@ -79,7 +80,7 @@ jobs: # access the environment. # # We only enable the environment for the rust-lang/rust repository, so that CI works on forks. - environment: ${{ ((github.repository == 'rust-lang/rust' && (github.ref == 'refs/heads/try-perf' || github.ref == 'refs/heads/automation/bors/try' || github.ref == 'refs/heads/automation/bors/auto')) && 'bors') || '' }} + environment: ${{ ((github.repository == 'rust-lang/rust' && (github.ref == 'refs/heads/try-perf' || github.ref == 'refs/heads/automation/bors/try' || github.ref == 'refs/heads/automation/bors/try-perf' || github.ref == 'refs/heads/automation/bors/auto')) && 'bors') || '' }} env: CI_JOB_NAME: ${{ matrix.name }} CI_JOB_DOC_URL: ${{ matrix.doc_url }} diff --git a/src/ci/citool/src/main.rs b/src/ci/citool/src/main.rs index 9b9cbe3862e39..8afda476ea68f 100644 --- a/src/ci/citool/src/main.rs +++ b/src/ci/citool/src/main.rs @@ -40,7 +40,9 @@ impl GitHubContext { fn get_run_type(&self) -> Option { match (self.event_name.as_str(), self.branch_ref.as_str()) { ("pull_request", _) => Some(RunType::PullRequest), - ("push", "refs/heads/try-perf") => Some(RunType::TryJob { job_patterns: None }), + ("push", "refs/heads/automation/bors/try-perf" | "refs/heads/try-perf") => { + Some(RunType::TryJob { job_patterns: None }) + } ("push", "refs/heads/automation/bors/try") => { let patterns = self.get_try_job_patterns(); let patterns = if !patterns.is_empty() { Some(patterns) } else { None }; From 1bd2075273fde57559daa0e84211859bbcda84cd Mon Sep 17 00:00:00 2001 From: Augie Fackler Date: Fri, 7 Aug 2026 08:20:42 -0400 Subject: [PATCH 55/57] rustc_codegen_llvm: handle sm_101* features being an alias LLVM 24 moved sm_101{,a,f} features to just be an alias for the matching sm_110 feature. Even though the breaking change in LLVM didn't introduce the 110 flavors, they appear to not exist in older LLVMs so we just gate on LLVM 24. --- compiler/rustc_codegen_llvm/src/llvm_util.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 6892e616e1f11..767da26858e46 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -252,6 +252,12 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option None, s => Some(LLVMFeature::new(s)), }, + Arch::Nvptx64 => match s { + "sm_101" if major >= 24 => Some(LLVMFeature::new("sm_110")), + "sm_101a" if major >= 24 => Some(LLVMFeature::new("sm_110a")), + "sm_101f" if major >= 24 => Some(LLVMFeature::new("sm_110f")), + s => Some(LLVMFeature::new(s)), + }, // Filter out features that are not supported by the current LLVM version Arch::PowerPC | Arch::PowerPC64 => match s { "power8-crypto" => Some(LLVMFeature::new("crypto")), From 6eac4e4d3901c71463235ed6ed25d4e3db48127e Mon Sep 17 00:00:00 2001 From: MarcoIeni <11428655+MarcoIeni@users.noreply.github.com> Date: Fri, 7 Aug 2026 16:19:23 +0200 Subject: [PATCH 56/57] renovate: clarify that vulnerability PRs are opened automatically --- .github/renovate.json5 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 1827901fc041e..390a41c64931b 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -19,8 +19,13 @@ "src/doc/book", "src/doc/reference" ], - // Require manual approval from the Dependency Dashboard before opening PRs + // Require manual approval from the Dependency Dashboard before opening PRs, + // except for the update types explicitly configured below. "dependencyDashboardApproval": true, + // No dashboard approval necessary for security updates + "vulnerabilityAlerts": { + "dependencyDashboardApproval": false + }, // Renovate shouldn't update a PR if it is in the bors merge queue. "stopUpdatingLabel": "S-waiting-on-bors", "packageRules": [ From f231e430bc212fe298420367e18b42b3167c9cfb Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 7 Aug 2026 10:47:29 +1000 Subject: [PATCH 57/57] Streamline `canonicalize_param_env` There are two canonicalization steps done by `canonicalize_input` and `canonicalize_param_env`: `env` (possible cached) and `rest`. `canonicalize_param_env` does the `env` step. It returns several pieces of a canonicalizer (either from the cache or by constructing a canonicalizer) and then `canonicalize_input` uses those parts to construct a second canonicalizer, which it uses for `rest`. This commit changes things so that `canonicalize_param_env` does the `env` part (if necessary) and then returns a canonicalizer that can do the `rest` part. I find this easier to read. In particular, we no longer construct an `env` canonicalizer when it's not necessary, we immediately construct the `rest` canonicalizer. --- .../src/canonical/canonicalizer.rs | 135 +++++++++--------- 1 file changed, 66 insertions(+), 69 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs index 1ebbeaec482e2..73d969a398198 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs @@ -1,3 +1,5 @@ +use std::collections::hash_map::Entry; + use rustc_type_ir::data_structures::{HashMap, ensure_sufficient_stack}; use rustc_type_ir::inherent::*; use rustc_type_ir::solve::{Goal, QueryInput}; @@ -113,68 +115,80 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { Canonical { max_universe, var_kinds, value } } - fn canonicalize_param_env( - delegate: &'a D, - param_env: I::ParamEnv, - ) -> ( - I::ParamEnv, - ThinVec, - Vec>, - HashMap, - ) { + // The return value is the canonicalized `param_env`, plus a canonicalizer suitable for + // canonicalizing the rest of the input. (For efficiency, and when appropriate, the returned + // canonicalizer will be the same one used on `param_env`, with suitable modifications.) + fn canonicalize_param_env(delegate: &'a D, param_env: I::ParamEnv) -> (I::ParamEnv, Self) { if !param_env.has_type_flags(NEEDS_CANONICAL) { - return (param_env, ThinVec::new(), Vec::new(), Default::default()); + let rest_canonicalizer = Canonicalizer::new( + delegate, + CanonicalizeMode::Input(CanonicalizeInputKind::Predicate), + ); + + return (param_env, rest_canonicalizer); } + // Do the `env` canonicalization, and then convert the canonicalizer to `rest` form for + // subsequent use. + let do_env_and_make_rest = || { + let mut env_canonicalizer = Canonicalizer::new( + delegate, + CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), + ); + let param_env = param_env.fold_with(&mut env_canonicalizer); + + // We do not reuse the cache as it may contain entries whose canonicalized + // value contains `'static`. While we could alternatively handle this by + // checking for `'static` when using cached entries, this does not + // feel worth the effort. I do not expect that a `ParamEnv` will ever + // contain large enough types for caching to be necessary. + debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty()); + let rest_canonicalizer = Canonicalizer { + canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::Predicate), + cache: Default::default(), + ..env_canonicalizer + }; + + (param_env, rest_canonicalizer) + }; + // Check whether we can use the global cache for this param_env. As we only use // the `param_env` itself as the cache key, considering any additional information - // durnig its canonicalization would be incorrect. We always canonicalize region + // during its canonicalization would be incorrect. We always canonicalize region // inference variables in a separate universe, so these are fine. However, we do // track the universe of type and const inference variables so these must not be // globally cached. We don't rely on any additional information when canonicalizing // placeholders. if !param_env.has_non_region_infer() { - delegate.cx().with_canonical_param_env_cache(|cache| { - let entry = cache.0.entry(param_env).or_insert_with(|| { - let mut env_canonicalizer = Canonicalizer::new( + delegate.cx().with_canonical_param_env_cache(|cache| match cache.0.entry(param_env) { + Entry::Vacant(e) => { + // Cache miss. Do `env` canonicalization and get `rest_canonicalizer`, and + // fill in the cache entry. + let (param_env, rest_canonicalizer) = do_env_and_make_rest(); + e.insert(CanonicalParamEnvCacheEntry { + param_env, + variables: rest_canonicalizer.variables.clone(), + var_kinds: rest_canonicalizer.var_kinds.clone(), + variable_lookup_table: rest_canonicalizer.variable_lookup_table.clone(), + }); + (param_env, rest_canonicalizer) + } + Entry::Occupied(e) => { + // Cache hit; no canonicalization required. Just set up `rest_canonicalizer`. + let e = e.get(); + let mut rest_canonicalizer = Canonicalizer::new( delegate, - CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), + CanonicalizeMode::Input(CanonicalizeInputKind::Predicate), ); - let param_env = param_env.fold_with(&mut env_canonicalizer); - debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty()); - CanonicalParamEnvCacheEntry { - param_env, - variable_lookup_table: env_canonicalizer.variable_lookup_table, - var_kinds: env_canonicalizer.var_kinds, - variables: env_canonicalizer.variables, - } - }); - - // The obvious thing to do here is `variables.clone()`. But this `new`+`extend` - // combination results in the variables having more spare capacity, which avoids - // some later allocations and makes things a little faster. - let mut variables = ThinVec::new(); - variables.extend(entry.variables.iter().copied()); - ( - entry.param_env, - variables, - entry.var_kinds.clone(), - entry.variable_lookup_table.clone(), - ) + rest_canonicalizer.variables.extend(e.variables.iter().copied()); + rest_canonicalizer.var_kinds.clone_from(&e.var_kinds); + rest_canonicalizer.variable_lookup_table.clone_from(&e.variable_lookup_table); + (e.param_env, rest_canonicalizer) + } }) } else { - let mut env_canonicalizer = Canonicalizer::new( - delegate, - CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), - ); - let param_env = param_env.fold_with(&mut env_canonicalizer); - debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty()); - ( - param_env, - env_canonicalizer.variables, - env_canonicalizer.var_kinds, - env_canonicalizer.variable_lookup_table, - ) + // Do `env` canonicalization and get `rest_canonicalizer`. + do_env_and_make_rest() } } @@ -190,27 +204,10 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { delegate: &'a D, input: QueryInput, ) -> (ThinVec, ty::Canonical>) { - // First canonicalize the `param_env` while keeping `'static` - let (param_env, variables, var_kinds, variable_lookup_table) = - Canonicalizer::canonicalize_param_env(delegate, input.goal.param_env); - - // Then canonicalize the rest of the input without keeping `'static` - // while *mostly* reusing the canonicalizer from above. - // - // We do not reuse the cache as it may contain entries whose canonicalized - // value contains `'static`. While we could alternatively handle this by - // checking for `'static` when using cached entries, this does not - // feel worth the effort. I do not expect that a `ParamEnv` will ever - // contain large enough types for caching to be necessary. - let mut rest_canonicalizer = Canonicalizer { - variables, - variable_lookup_table, - var_kinds, - ..Canonicalizer::new( - delegate, - CanonicalizeMode::Input(CanonicalizeInputKind::Predicate), - ) - }; + // First canonicalize the `param_env` while keeping `'static`. This produces a + // canonicalizer that can canonicalize the rest of the input without keeping `'static`. + let (param_env, mut rest_canonicalizer) = + Self::canonicalize_param_env(delegate, input.goal.param_env); let predicate = input.goal.predicate; let predicate = predicate.fold_with(&mut rest_canonicalizer);