From 60f100b497e7d34bb69d0018d03894fba8b6e2cb Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Sun, 12 Jul 2026 20:24:48 +0000 Subject: [PATCH 01/63] Add Instance::requires_caller_location to rustc_public Expose the internal `requires_caller_location` query through rustc_public so tools can detect when an instance has an implicit extra `&'static Location<'static>` argument in its ABI that is not present in the MIR body signature. Fixes https://github.com/rust-lang/rustc_public/issues/123 --- .../rustc_public/src/compiler_interface.rs | 8 ++ compiler/rustc_public/src/mir/mono.rs | 12 ++ .../rustc_public_bridge/src/context/impls.rs | 9 ++ .../rustc_public/check_track_caller.rs | 122 ++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 tests/ui-fulldeps/rustc_public/check_track_caller.rs diff --git a/compiler/rustc_public/src/compiler_interface.rs b/compiler/rustc_public/src/compiler_interface.rs index 5512371b68b3a..5497d7297b24b 100644 --- a/compiler/rustc_public/src/compiler_interface.rs +++ b/compiler/rustc_public/src/compiler_interface.rs @@ -637,6 +637,14 @@ impl<'tcx> CompilerInterface<'tcx> { }) } + /// Check if this instance requires a caller location argument. + pub(crate) fn instance_requires_caller_location(&self, def: InstanceDef) -> bool { + self.with_cx(|tables, cx| { + let instance = tables.instances[def]; + cx.instance_requires_caller_location(instance) + }) + } + /// Check if this is an empty DropGlue shim. pub(crate) fn is_empty_drop_shim(&self, def: InstanceDef) -> bool { self.with_cx(|tables, cx| { diff --git a/compiler/rustc_public/src/mir/mono.rs b/compiler/rustc_public/src/mir/mono.rs index ab939a5535149..4abbb004a8e24 100644 --- a/compiler/rustc_public/src/mir/mono.rs +++ b/compiler/rustc_public/src/mir/mono.rs @@ -166,6 +166,18 @@ impl Instance { self.kind == InstanceKind::Shim && with(|cx| cx.is_empty_drop_shim(self.def)) } + /// Check whether this instance requires a caller location argument. + /// + /// Functions annotated with `#[track_caller]` have an implicit extra + /// `&'static core::panic::Location<'static>` argument appended to their ABI. + /// This argument is not present in the MIR body's signature. + /// + /// When this returns `true`, the instance's `fn_abi()` will have one additional + /// argument compared to the MIR body's parameter list. + pub fn requires_caller_location(&self) -> bool { + with(|cx| cx.instance_requires_caller_location(self.def)) + } + /// Try to constant evaluate the instance into a constant with the given type. /// /// This can be used to retrieve a constant that represents an intrinsic return such as diff --git a/compiler/rustc_public_bridge/src/context/impls.rs b/compiler/rustc_public_bridge/src/context/impls.rs index 87a8661edeb65..75f63871ea361 100644 --- a/compiler/rustc_public_bridge/src/context/impls.rs +++ b/compiler/rustc_public_bridge/src/context/impls.rs @@ -648,6 +648,15 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { matches!(instance.def, ty::InstanceKind::Shim(ty::ShimKind::DropGlue(_, None))) } + /// Check if this instance requires a caller location argument. + /// + /// Functions with `#[track_caller]` have an implicit extra + /// `&'static core::panic::Location<'static>` argument appended to their ABI, + /// which is not visible in their MIR body signature. + pub fn instance_requires_caller_location(&self, instance: ty::Instance<'tcx>) -> bool { + instance.def.requires_caller_location(self.tcx) + } + /// Convert a non-generic crate item into an instance. /// This function will panic if the item is generic. pub fn mono_instance(&self, def_id: DefId) -> Instance<'tcx> { diff --git a/tests/ui-fulldeps/rustc_public/check_track_caller.rs b/tests/ui-fulldeps/rustc_public/check_track_caller.rs new file mode 100644 index 0000000000000..bf802bc99ddf4 --- /dev/null +++ b/tests/ui-fulldeps/rustc_public/check_track_caller.rs @@ -0,0 +1,122 @@ +//@ run-pass +//! Test that users can query `Instance::requires_caller_location` to detect +//! `#[track_caller]` functions and the implicit extra argument in their ABI. + +//@ ignore-stage1 +//@ ignore-cross-compile +//@ ignore-remote +//@ edition: 2021 + +#![feature(rustc_private)] + +extern crate rustc_driver; +extern crate rustc_interface; +extern crate rustc_middle; +#[macro_use] +extern crate rustc_public; + +use std::io::Write; +use std::ops::ControlFlow; + +use rustc_public::crate_def::CrateDef; +use rustc_public::mir::mono::Instance; +use rustc_public::mir::TerminatorKind; +use rustc_public::ty::{RigidTy, TyKind}; + +const CRATE_NAME: &str = "input"; + +fn test_stable_mir() -> ControlFlow<()> { + let items = rustc_public::all_local_items(); + + let tracked = items + .iter() + .find(|item| item.name() == "input::tracked_fn") + .expect("missing tracked_fn"); + let not_tracked = items + .iter() + .find(|item| item.name() == "input::not_tracked_fn") + .expect("missing not_tracked_fn"); + let caller = + items.iter().find(|item| item.name() == "input::caller").expect("missing caller"); + + let tracked_instance = Instance::try_from(*tracked).unwrap(); + let not_tracked_instance = Instance::try_from(*not_tracked).unwrap(); + + // Check requires_caller_location + assert!( + tracked_instance.requires_caller_location(), + "tracked_fn should require caller location" + ); + assert!( + !not_tracked_instance.requires_caller_location(), + "not_tracked_fn should NOT require caller location" + ); + + // Verify that the ABI reflects the extra argument. + let tracked_abi = tracked_instance.fn_abi().unwrap(); + let not_tracked_abi = not_tracked_instance.fn_abi().unwrap(); + + // tracked_fn(u32) -> u32: MIR has 1 arg, ABI has 2 (extra &Location) + assert_eq!(tracked_abi.args.len(), 2, "tracked_fn ABI should have 2 args"); + // not_tracked_fn(u32) -> u32: MIR has 1 arg, ABI has 1 + assert_eq!(not_tracked_abi.args.len(), 1, "not_tracked_fn ABI should have 1 arg"); + + // Check that calling a #[track_caller] function from the caller's body + // resolves to an instance that requires caller location. + let caller_instance = Instance::try_from(*caller).unwrap(); + let caller_body = caller_instance.body().unwrap(); + for bb in &caller_body.blocks { + if let TerminatorKind::Call { func, .. } = &bb.terminator.kind { + let TyKind::RigidTy(RigidTy::FnDef(def, args)) = + func.ty(caller_body.locals()).unwrap().kind() + else { + continue; + }; + let callee = Instance::resolve(def, &args).unwrap(); + if callee.mangled_name().contains("tracked_fn") { + assert!( + callee.requires_caller_location(), + "resolved callee should require caller location" + ); + } + } + } + + ControlFlow::Continue(()) +} + +fn main() { + let path = "track_caller_input.rs"; + generate_input(&path).unwrap(); + let args = &[ + "rustc".to_string(), + "-Cpanic=abort".to_string(), + "--crate-type=lib".to_string(), + "--crate-name".to_string(), + CRATE_NAME.to_string(), + path.to_string(), + ]; + run!(args, test_stable_mir).unwrap(); +} + +fn generate_input(path: &str) -> std::io::Result<()> { + let mut file = std::fs::File::create(path)?; + write!( + file, + r#" + #[track_caller] + pub fn tracked_fn(x: u32) -> u32 {{ + x + 1 + }} + + pub fn not_tracked_fn(x: u32) -> u32 {{ + x + 1 + }} + + pub fn caller() -> u32 {{ + tracked_fn(42) + }} + "# + )?; + Ok(()) +} From a924b6de4c47ba93356385f8c3d3f7f7e2f1e9df Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Sun, 12 Jul 2026 20:30:45 +0000 Subject: [PATCH 02/63] Add Span::caller_location to rustc_public Add an API to construct the `&'static Location<'static>` constant that must be passed as the implicit extra argument when calling a `#[track_caller]` function. Users provide the span of the call site (or the callee's definition span for reify shim fallback). Fixes https://github.com/rust-lang/rustc_public/issues/62 --- .../rustc_public/src/compiler_interface.rs | 8 +++++++ compiler/rustc_public/src/ty.rs | 8 +++++++ .../rustc_public_bridge/src/context/impls.rs | 10 ++++++++ .../rustc_public/check_track_caller.rs | 23 ++++++++++++++++--- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_public/src/compiler_interface.rs b/compiler/rustc_public/src/compiler_interface.rs index 5497d7297b24b..1f2a038765d4b 100644 --- a/compiler/rustc_public/src/compiler_interface.rs +++ b/compiler/rustc_public/src/compiler_interface.rs @@ -496,6 +496,14 @@ impl<'tcx> CompilerInterface<'tcx> { }) } + /// Create a caller location constant from a span. + pub(crate) fn span_as_caller_location(&self, span: Span) -> MirConst { + self.with_cx(|tables, cx| { + let sp = tables.spans[span]; + cx.span_as_caller_location(sp).stable(tables, cx) + }) + } + /// Create a new constant that represents the given string value. pub(crate) fn new_const_str(&self, value: &str) -> MirConst { self.with_cx(|tables, cx| cx.new_const_str(value).stable(tables, cx)) diff --git a/compiler/rustc_public/src/ty.rs b/compiler/rustc_public/src/ty.rs index 8a739df7d9ad2..ada78c557e265 100644 --- a/compiler/rustc_public/src/ty.rs +++ b/compiler/rustc_public/src/ty.rs @@ -287,6 +287,14 @@ impl Span { pub fn diagnostic(&self) -> String { with(|c| c.span_to_string(*self)) } + + /// Create a `&'static core::panic::Location<'static>` constant from this span. + /// + /// This is the value that must be passed as the implicit extra argument + /// when calling a `#[track_caller]` function. + pub fn as_caller_location(&self) -> MirConst { + with(|c| c.span_as_caller_location(*self)) + } } #[derive(Clone, Copy, Debug, Serialize)] diff --git a/compiler/rustc_public_bridge/src/context/impls.rs b/compiler/rustc_public_bridge/src/context/impls.rs index 75f63871ea361..a018187e943e8 100644 --- a/compiler/rustc_public_bridge/src/context/impls.rs +++ b/compiler/rustc_public_bridge/src/context/impls.rs @@ -485,6 +485,16 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { ty::Const::zero_sized(self.tcx, ty_internal) } + /// Create a caller location constant from a span. + /// + /// This produces a `&'static core::panic::Location<'static>` constant, + /// which is the implicit extra argument for `#[track_caller]` functions. + pub fn span_as_caller_location(&self, span: Span) -> MirConst<'tcx> { + let val = self.tcx.span_as_caller_location(span); + let ty = self.tcx.caller_location_ty(); + MirConst::from_value(val, ty) + } + /// Create a new constant that represents the given string value. pub fn new_const_str(&self, value: &str) -> MirConst<'tcx> { let ty = Ty::new_static_str(self.tcx); diff --git a/tests/ui-fulldeps/rustc_public/check_track_caller.rs b/tests/ui-fulldeps/rustc_public/check_track_caller.rs index bf802bc99ddf4..8da55f1129d33 100644 --- a/tests/ui-fulldeps/rustc_public/check_track_caller.rs +++ b/tests/ui-fulldeps/rustc_public/check_track_caller.rs @@ -1,6 +1,7 @@ //@ run-pass //! Test that users can query `Instance::requires_caller_location` to detect -//! `#[track_caller]` functions and the implicit extra argument in their ABI. +//! `#[track_caller]` functions and the implicit extra argument in their ABI, +//! and that they can generate caller location constants via `Span::caller_location`. //@ ignore-stage1 //@ ignore-cross-compile @@ -21,7 +22,7 @@ use std::ops::ControlFlow; use rustc_public::crate_def::CrateDef; use rustc_public::mir::mono::Instance; use rustc_public::mir::TerminatorKind; -use rustc_public::ty::{RigidTy, TyKind}; +use rustc_public::ty::{ConstantKind, RigidTy, TyKind}; const CRATE_NAME: &str = "input"; @@ -62,7 +63,8 @@ fn test_stable_mir() -> ControlFlow<()> { assert_eq!(not_tracked_abi.args.len(), 1, "not_tracked_fn ABI should have 1 arg"); // Check that calling a #[track_caller] function from the caller's body - // resolves to an instance that requires caller location. + // resolves to an instance that requires caller location, and that we can + // generate a caller location constant from the call site span. let caller_instance = Instance::try_from(*caller).unwrap(); let caller_body = caller_instance.body().unwrap(); for bb in &caller_body.blocks { @@ -78,6 +80,21 @@ fn test_stable_mir() -> ControlFlow<()> { callee.requires_caller_location(), "resolved callee should require caller location" ); + + // Generate a caller location from the call site span. + let call_span = bb.terminator.source_info.span; + let location_const = call_span.as_caller_location(); + // The constant should be a reference type (&'static Location<'static>). + let ty_kind = location_const.ty().kind(); + assert!( + matches!(ty_kind, TyKind::RigidTy(RigidTy::Ref(..))), + "caller_location should produce a reference type, got: {ty_kind:?}" + ); + // The constant should be allocated (a scalar pointer to static data). + assert!( + matches!(location_const.kind(), ConstantKind::Allocated(..)), + "caller_location should produce an allocated constant" + ); } } } From 1e452c3b929f923cb5d657af63c3ebdc4fcd3f15 Mon Sep 17 00:00:00 2001 From: Paul Mabileau Date: Fri, 17 Jul 2026 12:18:45 +0200 Subject: [PATCH 03/63] Fix(lib/fs/win): Fall back on Win32 delete for Dir::remove_file The `test_dir_remove_file` test currently fails under Windows 7 on: ``` thread 'fs::tests::test_dir_remove_file' (2028) panicked at library/std/src/fs/tests.rs:2590:5: dir.remove_file("foo.txt") failed with: The parameter is incorrect. (os error 87) ``` Looking into it, this is because the `FILE_DISPOSITION_INFO_EX` variant of the `SetFileInformationByHandle` API is used while it is only available starting from Windows 10 1607. However, `FILE_DISPOSITION_INFO` is still available since Vista. This is therefore fixed by introducing a fallback to the latter API if the former fails. This is achieved by re-using some logic that is already available through `File::delete` and that does exactly this. Signed-off-by: Paul Mabileau --- library/std/src/sys/fs/windows/dir.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/library/std/src/sys/fs/windows/dir.rs b/library/std/src/sys/fs/windows/dir.rs index cd86f76bbcf83..5e69515b66599 100644 --- a/library/std/src/sys/fs/windows/dir.rs +++ b/library/std/src/sys/fs/windows/dir.rs @@ -6,7 +6,7 @@ use crate::os::windows::io::{ OwnedHandle, RawHandle, }; use crate::path::Path; -use crate::sys::api::{self, SetFileInformation, UnicodeStrRef, WinError}; +use crate::sys::api::{UnicodeStrRef, WinError}; use crate::sys::fs::windows::debug_path_handle; use crate::sys::fs::{File, FileAttr, OpenOptions}; use crate::sys::handle::Handle; @@ -127,16 +127,7 @@ impl Dir { let mut opts = OpenOptions::new(); opts.access_mode(c::DELETE); let handle = self.open_file_native(path, &opts, dir)?; - let info = c::FILE_DISPOSITION_INFO_EX { Flags: c::FILE_DISPOSITION_FLAG_DELETE }; - let result = unsafe { - c::SetFileInformationByHandle( - handle.as_raw_handle(), - c::FileDispositionInfoEx, - (&info).as_ptr(), - size_of::() as _, - ) - }; - if result == 0 { Err(api::get_last_error()).io_result() } else { Ok(()) } + File::from_inner(handle).delete().io_result() } fn rename_native(&self, from: &[u16], to_dir: &Self, to: &[u16], dir: bool) -> io::Result<()> { From 5c88a1c75a7bc1bc45a7adc834dd29478e2c9017 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Thu, 16 Jul 2026 23:05:33 +0000 Subject: [PATCH 04/63] [rpub] Replace Span::caller_location by Body's one Add a method Body::caller_location that resolves the correct caller location constant for a call to a #[track_caller] function, walking inlined source scopes to handle MIR inlining correctly. Body now stores source scope data as a public field. Body::new populates it with empty entries (no inlining info); bodies obtained from the compiler have full scope data populated. Span::as_caller_location is now pub(crate) since users should use Body::caller_location instead, which handles inlining correctly. --- compiler/rustc_public/src/mir/body.rs | 96 +++++++++++-- compiler/rustc_public/src/mir/visit.rs | 10 +- compiler/rustc_public/src/ty.rs | 5 +- .../src/unstable/convert/stable/mir.rs | 36 +++-- .../rustc_public/check_track_caller.rs | 131 +++++++++++------- 5 files changed, 202 insertions(+), 76 deletions(-) diff --git a/compiler/rustc_public/src/mir/body.rs b/compiler/rustc_public/src/mir/body.rs index 20682de5f4825..def6c837a2b85 100644 --- a/compiler/rustc_public/src/mir/body.rs +++ b/compiler/rustc_public/src/mir/body.rs @@ -20,10 +20,10 @@ pub struct Body { /// The first local is the return value pointer, followed by `arg_count` /// locals for the function arguments, followed by any user-declared /// variables and temporaries. - pub(super) locals: LocalDecls, + pub(crate) locals: LocalDecls, /// The number of arguments this function takes. - pub(super) arg_count: usize, + pub(crate) arg_count: usize, /// Debug information pertaining to user variables, including captures. pub var_debug_info: Vec, @@ -31,19 +31,29 @@ pub struct Body { /// Mark an argument (which must be a tuple) as getting passed as its individual components. /// /// This is used for the "rust-call" ABI such as closures. - pub(super) spread_arg: Option, + pub(crate) spread_arg: Option, /// The span that covers the entire function body. pub span: Span, + + /// Source scope information, used by [`Body::caller_location`] for inline-aware resolution. + /// + /// Invariants: + /// - All scope indices referenced by terminators and statements must be within bounds. + /// - `inlined_parent_scope` links must not form cycles. + pub(crate) source_scopes: Vec, } pub type BasicBlockIdx = usize; impl Body { - /// Constructs a `Body`. + /// Constructs a `Body` without inlining information. + /// + /// # Warning /// - /// A constructor is required to build a `Body` from outside the crate - /// because the `arg_count` and `locals` fields are private. + /// This constructor does not accept source scope data today. + /// [`Body::caller_location`] will fall back to the terminator's span, + /// which may be incorrect when MIR inlining is involved. pub fn new( blocks: Vec, locals: LocalDecls, @@ -52,13 +62,15 @@ impl Body { spread_arg: Option, span: Span, ) -> Self { - // If locals doesn't contain enough entries, it can lead to panics in - // `ret_local`, `arg_locals`, and `inner_locals`. assert!( locals.len() > arg_count, "A Body must contain at least a local for the return value and each of the function's arguments" ); - Self { blocks, locals, arg_count, var_debug_info, spread_arg, span } + let source_scopes = vec![ + SourceScopeInfo { inlined: None, inlined_parent_scope: None }; + max_scope(&blocks) as usize + 1 + ]; + Self { blocks, locals, arg_count, var_debug_info, spread_arg, span, source_scopes } } /// Return local that holds this function's return value. @@ -119,6 +131,44 @@ impl Body { pub fn spread_arg(&self) -> Option { self.spread_arg } + + /// Resolve the caller location for a call to a `#[track_caller]` function. + /// + /// Use this when generating the implicit `&'static Location<'static>` argument + /// for a call where [`Instance::requires_caller_location`] is true. + /// + /// Pass `inherited_location` if this body belongs to a `#[track_caller]` function + /// (the implicit parameter it received). Pass `None` otherwise. + /// + /// This method accounts for MIR inlining: when inlined `#[track_caller]` functions + /// are present, the terminator's span may not be the correct location. The method + /// walks the inlined scopes to resolve the right one. + /// + /// [`Instance::requires_caller_location`]: crate::mir::mono::Instance::requires_caller_location + pub fn caller_location( + &self, + terminator: &Terminator, + inherited_location: Option, + ) -> MirConst { + let mut span = terminator.source_info.span; + let mut scope = terminator.source_info.scope; + + while let Some(scope_data) = self.source_scopes.get(scope as usize) { + if let Some((track_caller, callsite_span)) = &scope_data.inlined { + if !track_caller { + return span.as_caller_location(); + } + span = *callsite_span; + } + + match scope_data.inlined_parent_scope { + Some(parent) => scope = parent, + None => break, + } + } + + inherited_location.unwrap_or_else(|| span.as_caller_location()) + } } type LocalDecls = Vec; @@ -748,6 +798,22 @@ impl VarDebugInfo { pub type SourceScope = u32; +/// Data about a source scope, used for caller location resolution. +/// +/// Each entry corresponds to a source scope in the MIR body. Most scopes have no +/// inlined data. For scopes introduced by MIR inlining, `inlined` records whether +/// the inlined callee is `#[track_caller]` and the span of the call site. +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] +pub(crate) struct SourceScopeInfo { + /// Present when this scope was introduced by inlining a function. + /// The `bool` is `true` if the inlined callee is `#[track_caller]`. + /// The `Span` is the call site where inlining occurred. + pub inlined: Option<(bool, Span)>, + /// Nearest (transitive) parent scope that was itself inlined. + /// Skips over intermediate scopes within the same inlined function body. + pub inlined_parent_scope: Option, +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize)] pub struct SourceInfo { pub span: Span, @@ -1103,3 +1169,15 @@ impl ProjectionElem { Ok(deref_ty.ty) } } + +/// Return the maximum scope index referenced by any terminator or statement in `blocks`. +fn max_scope(blocks: &[BasicBlock]) -> u32 { + blocks + .iter() + .flat_map(|bb| { + std::iter::once(bb.terminator.source_info.scope) + .chain(bb.statements.iter().map(|s| s.source_info.scope)) + }) + .max() + .unwrap_or(0) +} diff --git a/compiler/rustc_public/src/mir/visit.rs b/compiler/rustc_public/src/mir/visit.rs index 8cb3237bc4c83..d80c769cd8169 100644 --- a/compiler/rustc_public/src/mir/visit.rs +++ b/compiler/rustc_public/src/mir/visit.rs @@ -408,7 +408,15 @@ macro_rules! super_body { }; ($self:ident, $body:ident, ) => { - let Body { blocks, locals: _, arg_count, var_debug_info, spread_arg: _, span } = $body; + let Body { + blocks, + locals: _, + arg_count, + var_debug_info, + spread_arg: _, + span, + source_scopes: _, + } = $body; for bb in blocks { $self.visit_basic_block(bb); diff --git a/compiler/rustc_public/src/ty.rs b/compiler/rustc_public/src/ty.rs index ada78c557e265..359a4ab62b185 100644 --- a/compiler/rustc_public/src/ty.rs +++ b/compiler/rustc_public/src/ty.rs @@ -289,10 +289,7 @@ impl Span { } /// Create a `&'static core::panic::Location<'static>` constant from this span. - /// - /// This is the value that must be passed as the implicit extra argument - /// when calling a `#[track_caller]` function. - pub fn as_caller_location(&self) -> MirConst { + pub(crate) fn as_caller_location(&self) -> MirConst { with(|c| c.span_as_caller_location(*self)) } } diff --git a/compiler/rustc_public/src/unstable/convert/stable/mir.rs b/compiler/rustc_public/src/unstable/convert/stable/mir.rs index 4293d1bede421..6471b106ae757 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/mir.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/mir.rs @@ -7,7 +7,9 @@ use rustc_public_bridge::{Tables, bridge}; use crate::compiler_interface::BridgeTys; use crate::mir::alloc::GlobalAlloc; -use crate::mir::{ConstOperand, Statement, UserTypeProjection, VarDebugInfoFragment}; +use crate::mir::{ + ConstOperand, SourceScopeInfo, Statement, UserTypeProjection, VarDebugInfoFragment, +}; use crate::ty::{Allocation, ConstantKind, MirConst}; use crate::unstable::Stable; use crate::{Error, alloc, opaque}; @@ -20,8 +22,9 @@ impl<'tcx> Stable<'tcx> for mir::Body<'tcx> { tables: &mut Tables<'cx, BridgeTys>, cx: &CompilerCtxt<'cx, BridgeTys>, ) -> Self::T { - crate::mir::Body::new( - self.basic_blocks + crate::mir::Body { + blocks: self + .basic_blocks .iter() .map(|block| crate::mir::BasicBlock { terminator: block.terminator().stable(tables, cx), @@ -32,7 +35,8 @@ impl<'tcx> Stable<'tcx> for mir::Body<'tcx> { .collect(), }) .collect(), - self.local_decls + locals: self + .local_decls .iter() .map(|decl| crate::mir::LocalDecl { ty: decl.ty.stable(tables, cx), @@ -40,11 +44,25 @@ impl<'tcx> Stable<'tcx> for mir::Body<'tcx> { mutability: decl.mutability.stable(tables, cx), }) .collect(), - self.arg_count, - self.var_debug_info.iter().map(|info| info.stable(tables, cx)).collect(), - self.spread_arg.stable(tables, cx), - self.span.stable(tables, cx), - ) + arg_count: self.arg_count, + var_debug_info: self + .var_debug_info + .iter() + .map(|info| info.stable(tables, cx)) + .collect(), + spread_arg: self.spread_arg.stable(tables, cx), + span: self.span.stable(tables, cx), + source_scopes: self + .source_scopes + .iter() + .map(|scope_data| SourceScopeInfo { + inlined: scope_data.inlined.map(|(instance, span)| { + (instance.def.requires_caller_location(cx.tcx), span.stable(tables, cx)) + }), + inlined_parent_scope: scope_data.inlined_parent_scope.map(|s| s.as_u32()), + }) + .collect(), + } } } diff --git a/tests/ui-fulldeps/rustc_public/check_track_caller.rs b/tests/ui-fulldeps/rustc_public/check_track_caller.rs index 8da55f1129d33..a9d396a479fa5 100644 --- a/tests/ui-fulldeps/rustc_public/check_track_caller.rs +++ b/tests/ui-fulldeps/rustc_public/check_track_caller.rs @@ -1,7 +1,7 @@ //@ run-pass -//! Test that users can query `Instance::requires_caller_location` to detect -//! `#[track_caller]` functions and the implicit extra argument in their ABI, -//! and that they can generate caller location constants via `Span::caller_location`. +//! Test `#[track_caller]` support in rustc_public: +//! - `Instance::requires_caller_location` detects the implicit extra argument. +//! - `Body::caller_location` resolves the correct location constant. //@ ignore-stage1 //@ ignore-cross-compile @@ -21,85 +21,105 @@ use std::ops::ControlFlow; use rustc_public::crate_def::CrateDef; use rustc_public::mir::mono::Instance; -use rustc_public::mir::TerminatorKind; -use rustc_public::ty::{ConstantKind, RigidTy, TyKind}; +use rustc_public::mir::{Body, TerminatorKind}; +use rustc_public::ty::{ConstantKind, MirConst, RigidTy, TyKind}; const CRATE_NAME: &str = "input"; -fn test_stable_mir() -> ControlFlow<()> { +fn test_track_caller() -> ControlFlow<()> { let items = rustc_public::all_local_items(); - let tracked = items - .iter() - .find(|item| item.name() == "input::tracked_fn") - .expect("missing tracked_fn"); + test_requires_caller_location(&items); + test_caller_location_from_regular_fn(&items); + test_caller_location_propagation(&items); + + ControlFlow::Continue(()) +} + +/// Check `requires_caller_location` and the extra ABI argument. +fn test_requires_caller_location(items: &[rustc_public::CrateItem]) { + let tracked = + items.iter().find(|item| item.name() == "input::tracked_fn").expect("missing tracked_fn"); let not_tracked = items .iter() .find(|item| item.name() == "input::not_tracked_fn") .expect("missing not_tracked_fn"); - let caller = - items.iter().find(|item| item.name() == "input::caller").expect("missing caller"); let tracked_instance = Instance::try_from(*tracked).unwrap(); let not_tracked_instance = Instance::try_from(*not_tracked).unwrap(); - // Check requires_caller_location - assert!( - tracked_instance.requires_caller_location(), - "tracked_fn should require caller location" - ); - assert!( - !not_tracked_instance.requires_caller_location(), - "not_tracked_fn should NOT require caller location" - ); - - // Verify that the ABI reflects the extra argument. - let tracked_abi = tracked_instance.fn_abi().unwrap(); - let not_tracked_abi = not_tracked_instance.fn_abi().unwrap(); + assert!(tracked_instance.requires_caller_location()); + assert!(!not_tracked_instance.requires_caller_location()); // tracked_fn(u32) -> u32: MIR has 1 arg, ABI has 2 (extra &Location) + let tracked_abi = tracked_instance.fn_abi().unwrap(); assert_eq!(tracked_abi.args.len(), 2, "tracked_fn ABI should have 2 args"); + // not_tracked_fn(u32) -> u32: MIR has 1 arg, ABI has 1 + let not_tracked_abi = not_tracked_instance.fn_abi().unwrap(); assert_eq!(not_tracked_abi.args.len(), 1, "not_tracked_fn ABI should have 1 arg"); +} - // Check that calling a #[track_caller] function from the caller's body - // resolves to an instance that requires caller location, and that we can - // generate a caller location constant from the call site span. +/// Check that `Body::caller_location` resolves from the call site span for regular functions. +fn test_caller_location_from_regular_fn(items: &[rustc_public::CrateItem]) { + let caller = items.iter().find(|item| item.name() == "input::caller").expect("missing caller"); + let caller_instance = Instance::try_from(*caller).unwrap(); + assert!(!caller_instance.requires_caller_location()); + + let body = caller_instance.body().unwrap(); + let location = resolve_tracked_call_location(&body, None); + + // The result should be a &'static Location<'static> constant. + assert!( + matches!(location.ty().kind(), TyKind::RigidTy(RigidTy::Ref(..))), + "caller_location should produce a reference type" + ); + assert!( + matches!(location.kind(), ConstantKind::Allocated(..)), + "caller_location should produce an allocated constant" + ); +} + +/// Check that `Body::caller_location` propagates the inherited location for `#[track_caller]` fns. +fn test_caller_location_propagation(items: &[rustc_public::CrateItem]) { + let caller = items.iter().find(|item| item.name() == "input::caller").expect("missing caller"); let caller_instance = Instance::try_from(*caller).unwrap(); let caller_body = caller_instance.body().unwrap(); - for bb in &caller_body.blocks { + + let wrapper = items + .iter() + .find(|item| item.name() == "input::tracked_wrapper") + .expect("missing tracked_wrapper"); + let wrapper_instance = Instance::try_from(*wrapper).unwrap(); + assert!(wrapper_instance.requires_caller_location()); + + let wrapper_body = wrapper_instance.body().unwrap(); + + // Use the location resolved from caller() as the inherited value. + let inherited = resolve_tracked_call_location(&caller_body, None); + + // tracked_wrapper is #[track_caller], so pass the inherited location. + // Without inlining, the inherited value should be returned as-is. + let result = resolve_tracked_call_location(&wrapper_body, Some(inherited.clone())); + assert_eq!(result, inherited, "inherited location should be propagated through"); +} + +/// Find the first call to a `#[track_caller]` function in the body and resolve its location. +fn resolve_tracked_call_location(body: &Body, inherited: Option) -> MirConst { + for bb in &body.blocks { if let TerminatorKind::Call { func, .. } = &bb.terminator.kind { let TyKind::RigidTy(RigidTy::FnDef(def, args)) = - func.ty(caller_body.locals()).unwrap().kind() + func.ty(body.locals()).unwrap().kind() else { continue; }; let callee = Instance::resolve(def, &args).unwrap(); - if callee.mangled_name().contains("tracked_fn") { - assert!( - callee.requires_caller_location(), - "resolved callee should require caller location" - ); - - // Generate a caller location from the call site span. - let call_span = bb.terminator.source_info.span; - let location_const = call_span.as_caller_location(); - // The constant should be a reference type (&'static Location<'static>). - let ty_kind = location_const.ty().kind(); - assert!( - matches!(ty_kind, TyKind::RigidTy(RigidTy::Ref(..))), - "caller_location should produce a reference type, got: {ty_kind:?}" - ); - // The constant should be allocated (a scalar pointer to static data). - assert!( - matches!(location_const.kind(), ConstantKind::Allocated(..)), - "caller_location should produce an allocated constant" - ); + if callee.requires_caller_location() { + return body.caller_location(&bb.terminator, inherited); } } } - - ControlFlow::Continue(()) + panic!("no call to a #[track_caller] function found in body"); } fn main() { @@ -113,7 +133,7 @@ fn main() { CRATE_NAME.to_string(), path.to_string(), ]; - run!(args, test_stable_mir).unwrap(); + run!(args, test_track_caller).unwrap(); } fn generate_input(path: &str) -> std::io::Result<()> { @@ -133,6 +153,11 @@ fn generate_input(path: &str) -> std::io::Result<()> { pub fn caller() -> u32 {{ tracked_fn(42) }} + + #[track_caller] + pub fn tracked_wrapper(x: u32) -> u32 {{ + tracked_fn(x) + }} "# )?; Ok(()) From 10100f263cfc039ae167363293482f82adc855d1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 21 Jul 2026 10:26:36 -0700 Subject: [PATCH 05/63] Update wasm-component-ld to 0.5.27 Keeping up-to-date --- Cargo.lock | 74 +++++++++++++------------- src/tools/wasm-component-ld/Cargo.toml | 2 +- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5a3a9974fe5e..fd9f5d3782066 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -871,7 +871,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1284,7 +1284,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1411,7 +1411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2198,7 +2198,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2685,7 +2685,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5113,7 +5113,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5545,7 +5545,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5564,7 +5564,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6367,9 +6367,9 @@ dependencies = [ [[package]] name = "wasm-component-ld" -version = "0.5.26" +version = "0.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51a12709376d4ce64f472699500db3b0e5902cc2bef16fb6ca3098bfdac032fa" +checksum = "5fd1ba338b2c06654988a76b13f38cad9a1c738e3275bf71ecf70eeac5d98eb4" dependencies = [ "anyhow", "clap", @@ -6378,12 +6378,12 @@ dependencies = [ "libc", "tempfile", "wasi-preview1-component-adapter-provider", - "wasmparser 0.253.0", + "wasmparser 0.254.0", "wat", "windows-sys 0.61.2", "winsplit", - "wit-component 0.253.0", - "wit-parser 0.253.0", + "wit-component 0.254.0", + "wit-parser 0.254.0", ] [[package]] @@ -6415,12 +6415,12 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.253.0" +version = "0.254.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59972d6cd272259de647b7c1f1912e45e289c75ffd4be04e10695507cd7e1b59" +checksum = "09480d646178e5fdd12bb06e812d0af9a3a191dbc9cd697fdc86687beade7393" dependencies = [ "leb128fmt", - "wasmparser 0.253.0", + "wasmparser 0.254.0", ] [[package]] @@ -6437,14 +6437,14 @@ dependencies = [ [[package]] name = "wasm-metadata" -version = "0.253.0" +version = "0.254.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3f45816ef616806f48498bcd831377de578c4fa51db0c83ab8ceb78cc13523b" +checksum = "b01df5f3b4ca7881e843f3bc0fb8a3905d79c68692250dcb8e33e698705ccdb6" dependencies = [ "anyhow", "indexmap", - "wasm-encoder 0.253.0", - "wasmparser 0.253.0", + "wasm-encoder 0.254.0", + "wasmparser 0.254.0", ] [[package]] @@ -6481,9 +6481,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.253.0" +version = "0.254.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19db11f87d2486580e1e8b6f494c54df7e0566b87d0b599db843c24019667339" +checksum = "d5769a29f799fbab136aaf65b4fe5384cd7d93fe6fc9ba0dcb6c8382a1f16e27" dependencies = [ "bitflags", "hashbrown 0.17.0", @@ -6494,22 +6494,22 @@ dependencies = [ [[package]] name = "wast" -version = "253.0.0" +version = "254.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3264542f8965c5d84fb1085d924bfba9a6314bb228eff13a2de14d7627664d0" +checksum = "e7ed4dfc8f6b9fc38b231065e2cdfbf7359af5ab945990abf09658dcc63c3e32" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width 0.2.2", - "wasm-encoder 0.253.0", + "wasm-encoder 0.254.0", ] [[package]] name = "wat" -version = "1.253.0" +version = "1.254.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bfc5ce906144200c972ec617470aa35bd847472e170b26dde3e80541c674055" +checksum = "7127f7f9b8f127c879991cecd35f494e4628bae1b0874c681414d8d8831e952c" dependencies = [ "wast", ] @@ -6542,7 +6542,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6957,9 +6957,9 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.253.0" +version = "0.254.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbbd2500ac3488489ee8c6e59b79d7e47e6da5bfb019efd35d5dca57b78af624" +checksum = "b0e65bb94c369b3c4741ce3d1d2704b1fec93db7c540df0e521a097e7ceeb5be" dependencies = [ "anyhow", "bitflags", @@ -6968,10 +6968,10 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.253.0", - "wasm-metadata 0.253.0", - "wasmparser 0.253.0", - "wit-parser 0.253.0", + "wasm-encoder 0.254.0", + "wasm-metadata 0.254.0", + "wasmparser 0.254.0", + "wit-parser 0.254.0", ] [[package]] @@ -6994,9 +6994,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.253.0" +version = "0.254.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d997b8e5920fcbeec742b58e583325d6419a6aca617ae8075c406a61c65ba8a" +checksum = "1655131e4f7d3f0cb141f6eca71315ca40eff0f3d4de7cff0a82bacedd8c89b4" dependencies = [ "anyhow", "hashbrown 0.17.0", @@ -7008,7 +7008,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-ident", - "wasmparser 0.253.0", + "wasmparser 0.254.0", ] [[package]] diff --git a/src/tools/wasm-component-ld/Cargo.toml b/src/tools/wasm-component-ld/Cargo.toml index a07c2029f4b38..5a7e253b4fdb4 100644 --- a/src/tools/wasm-component-ld/Cargo.toml +++ b/src/tools/wasm-component-ld/Cargo.toml @@ -10,4 +10,4 @@ name = "wasm-component-ld" path = "src/main.rs" [dependencies] -wasm-component-ld = "0.5.26" +wasm-component-ld = "0.5.27" From 66d0fb88ebb382d2ff8d292062afe26263e1d3a7 Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 16 Apr 2026 12:34:20 +0200 Subject: [PATCH 06/63] stepping into `NormalizesTo` where-clauses may be productive --- .../src/solve/eval_ctxt/mod.rs | 12 +--- .../next-solver-region-resolution.rs | 3 +- .../next-solver-region-resolution.stderr | 22 +++++-- .../normalizes-to-is-not-productive-2.rs | 8 ++- .../cycles/normalizes-to-is-not-productive.rs | 14 ++-- .../normalizes-to-is-not-productive.stderr | 66 ++++++------------- 6 files changed, 54 insertions(+), 71 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index c3ccb46069063..16c119c6c2cdf 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -338,18 +338,12 @@ where // We currently only consider a cycle coinductive if it steps // into a where-clause of a coinductive trait. CurrentGoalKind::CoinductiveTrait => PathKind::Coinductive, - // While normalizing via an impl does step into a where-clause of - // an impl, accessing the associated item immediately steps out of - // it again. This means cycles/recursive calls are not guarded - // by impls used for normalization. - // - // See tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs - // for how this can go wrong. - CurrentGoalKind::ProjectionComputeAssocTermCandidate => PathKind::Inductive, // We probably want to make all traits coinductive in the future, // so we treat cycles involving where-clauses of not-yet coinductive // traits as ambiguous for now. - CurrentGoalKind::Misc => PathKind::Unknown, + CurrentGoalKind::Misc | CurrentGoalKind::ProjectionComputeAssocTermCandidate => { + PathKind::Unknown + } }, // Relating types is always unproductive. If we were to map proof trees to // corecursive functions as explained in #136824, relating types never diff --git a/tests/ui/specialization/min_specialization/next-solver-region-resolution.rs b/tests/ui/specialization/min_specialization/next-solver-region-resolution.rs index 16c0f80b1c8dd..a49fe6184890f 100644 --- a/tests/ui/specialization/min_specialization/next-solver-region-resolution.rs +++ b/tests/ui/specialization/min_specialization/next-solver-region-resolution.rs @@ -3,7 +3,7 @@ #![feature(min_specialization)] -trait Foo { +trait Foo { //~ ERROR cycle detected when coherence checking all impls of trait `Foo` type Item; } @@ -16,7 +16,6 @@ where } impl<'a, T> Foo for &T -//~^ ERROR: cycle detected when computing normalized predicates of `` where Self::Item: Baz, { diff --git a/tests/ui/specialization/min_specialization/next-solver-region-resolution.stderr b/tests/ui/specialization/min_specialization/next-solver-region-resolution.stderr index 4c2dc5e8e71fa..76d17e33c30ac 100644 --- a/tests/ui/specialization/min_specialization/next-solver-region-resolution.stderr +++ b/tests/ui/specialization/min_specialization/next-solver-region-resolution.stderr @@ -1,14 +1,26 @@ -error[E0391]: cycle detected when computing normalized predicates of `` +error[E0391]: cycle detected when coherence checking all impls of trait `Foo` + --> $DIR/next-solver-region-resolution.rs:6:1 + | +LL | trait Foo { + | ^^^^^^^^^ + | + = note: ...which requires building specialization graph of trait `Foo`... +note: ...which requires computing whether impls specialize one another... + --> $DIR/next-solver-region-resolution.rs:12:1 + | +LL | / impl<'a, T> Foo for &'a T +LL | | where +LL | | Self::Item: 'a, + | |___________________^ +note: ...which requires computing normalized predicates of ``... --> $DIR/next-solver-region-resolution.rs:18:1 | LL | / impl<'a, T> Foo for &T -LL | | LL | | where LL | | Self::Item: Baz, | |____________________^ - | - = note: ...which immediately requires computing normalized predicates of `` again -note: cycle used when computing whether impls specialize one another + = note: ...which again requires coherence checking all impls of trait `Foo`, completing the cycle +note: cycle used when checking that `` is well-formed --> $DIR/next-solver-region-resolution.rs:12:1 | LL | / impl<'a, T> Foo for &'a T diff --git a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.rs b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.rs index bb3540f9a214f..22a30cc4cd1d8 100644 --- a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.rs +++ b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.rs @@ -3,7 +3,7 @@ //@[next] compile-flags: -Znext-solver //@ check-pass -// Regression test for trait-system-refactor-initiative#176. +// Test from trait-system-refactor-initiative#176. // // Normalizing ` as IntoIterator>::IntoIter` has two candidates // inside of the function: @@ -13,7 +13,11 @@ // - where-clause requires ` as IntoIterator>::IntoIter eq Vec` // - normalize ` as IntoIterator>::IntoIter` again, cycle // -// We need to treat this cycle as an error to be able to use the actual impl. +// The blanket impl is unfortunately also a productive cycle, so we have to +// break this code, see trait-system-refactor-initiative#273. +// +// As we currently incorrectly treat aliases in the environment as rigid, this compiles +// even with the new solver, see #158643. fn test() where diff --git a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs index 83e300b077428..cb37d2c457a42 100644 --- a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs +++ b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs @@ -1,9 +1,10 @@ //@ ignore-compare-mode-next-solver (explicit) //@ compile-flags: -Znext-solver -// Make sure that stepping into impl where-clauses of `NormalizesTo` -// goals is unproductive. This must not compile, see the inline -// comments. +// A test for the cycle handling when stepping into where-clauses of `NormalizesTo`. +// Whether stepping into where-clauses is productive depends on how they are used. +// +// In this concrete test, the cycle must not be productive. trait Bound { fn method(); @@ -30,7 +31,7 @@ fn impls_bound() { // The where-clause requires `Foo: Trait` to hold to be wf. // If stepping into where-clauses during normalization is considered -// to be productive, this would be the case: +// to be productive here, this would be the case: // // - `Foo: Trait` // - via blanket impls, requires `Foo: Bound` @@ -41,12 +42,13 @@ fn generic() //~^ ERROR the trait bound `Foo: Bound` is not satisfied where >::Assoc: Bound, - //~^ ERROR the trait bound `Foo: Bound` is not satisfied + //~^ ERROR overflow evaluating the requirement `>::Assoc: Bound` + //~| ERROR overflow evaluating whether `>::Assoc` is well-formed { // Requires proving `Foo: Bound` by normalizing // `>::Assoc` to `Foo`. impls_bound::(); - //~^ ERROR the trait bound `Foo: Bound` is not satisfied + //~^ ERROR overflow evaluating the requirement `Foo: Bound` } fn main() { // Requires proving `>::Assoc: Bound`. diff --git a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.stderr b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.stderr index e18265190278a..7aeff2d06dabb 100644 --- a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.stderr +++ b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `Foo: Bound` is not satisfied - --> $DIR/normalizes-to-is-not-productive.rs:40:1 + --> $DIR/normalizes-to-is-not-productive.rs:41:1 | LL | / fn generic() LL | | @@ -8,76 +8,48 @@ LL | | >::Assoc: Bound, | |____________________________________^ unsatisfied trait bound | help: the trait `Bound` is not implemented for `Foo` - --> $DIR/normalizes-to-is-not-productive.rs:18:1 + --> $DIR/normalizes-to-is-not-productive.rs:19:1 | LL | struct Foo; | ^^^^^^^^^^ help: the trait `Bound` is implemented for `u32` - --> $DIR/normalizes-to-is-not-productive.rs:11:1 + --> $DIR/normalizes-to-is-not-productive.rs:12:1 | LL | impl Bound for u32 { | ^^^^^^^^^^^^^^^^^^ note: required for `Foo` to implement `Trait` - --> $DIR/normalizes-to-is-not-productive.rs:23:19 + --> $DIR/normalizes-to-is-not-productive.rs:24:19 | LL | impl Trait for T { | ----- ^^^^^^^^ ^ | | | unsatisfied trait bound introduced here -error[E0277]: the trait bound `Foo: Bound` is not satisfied - --> $DIR/normalizes-to-is-not-productive.rs:43:31 +error[E0275]: overflow evaluating the requirement `>::Assoc: Bound` + --> $DIR/normalizes-to-is-not-productive.rs:44:31 | LL | >::Assoc: Bound, - | ^^^^^ unsatisfied trait bound - | -help: the trait `Bound` is not implemented for `Foo` - --> $DIR/normalizes-to-is-not-productive.rs:18:1 - | -LL | struct Foo; - | ^^^^^^^^^^ -help: the trait `Bound` is implemented for `u32` - --> $DIR/normalizes-to-is-not-productive.rs:11:1 - | -LL | impl Bound for u32 { - | ^^^^^^^^^^^^^^^^^^ -note: required for `Foo` to implement `Trait` - --> $DIR/normalizes-to-is-not-productive.rs:23:19 - | -LL | impl Trait for T { - | ----- ^^^^^^^^ ^ - | | - | unsatisfied trait bound introduced here -note: required by a bound in `Bound` - --> $DIR/normalizes-to-is-not-productive.rs:8:1 + | ^^^^^ + +error[E0275]: overflow evaluating whether `>::Assoc` is well-formed + --> $DIR/normalizes-to-is-not-productive.rs:44:31 | -LL | / trait Bound { -LL | | fn method(); -LL | | } - | |_^ required by this bound in `Bound` +LL | >::Assoc: Bound, + | ^^^^^ -error[E0277]: the trait bound `Foo: Bound` is not satisfied - --> $DIR/normalizes-to-is-not-productive.rs:48:19 +error[E0275]: overflow evaluating the requirement `Foo: Bound` + --> $DIR/normalizes-to-is-not-productive.rs:50:19 | LL | impls_bound::(); - | ^^^ unsatisfied trait bound - | -help: the trait `Bound` is not implemented for `Foo` - --> $DIR/normalizes-to-is-not-productive.rs:18:1 + | ^^^ | -LL | struct Foo; - | ^^^^^^^^^^ -help: the trait `Bound` is implemented for `u32` - --> $DIR/normalizes-to-is-not-productive.rs:11:1 - | -LL | impl Bound for u32 { - | ^^^^^^^^^^^^^^^^^^ note: required by a bound in `impls_bound` - --> $DIR/normalizes-to-is-not-productive.rs:27:19 + --> $DIR/normalizes-to-is-not-productive.rs:28:19 | LL | fn impls_bound() { | ^^^^^ required by this bound in `impls_bound` -error: aborting due to 3 previous errors +error: aborting due to 4 previous errors -For more information about this error, try `rustc --explain E0277`. +Some errors have detailed explanations: E0275, E0277. +For more information about an error, try `rustc --explain E0275`. From aabf824843e0a5f9b1c72ec975b9bf8f03c429d3 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Wed, 22 Jul 2026 19:05:02 +0200 Subject: [PATCH 07/63] optimization: don't look for diagnostic/canonical items without rustc_attrs enabled --- compiler/rustc_passes/src/canonical_symbols.rs | 15 +++++++++------ compiler/rustc_passes/src/diagnostic_items.rs | 16 ++++++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_passes/src/canonical_symbols.rs b/compiler/rustc_passes/src/canonical_symbols.rs index 746758d3e36f2..c823453ef17f4 100644 --- a/compiler/rustc_passes/src/canonical_symbols.rs +++ b/compiler/rustc_passes/src/canonical_symbols.rs @@ -1,8 +1,8 @@ use rustc_hir::{CanonicalSymbols, ForeignItemId, find_attr}; use rustc_middle::query::{LocalCrate, Providers}; use rustc_middle::ty::{Instance, List, TyCtxt}; -use rustc_span::Symbol; use rustc_span::def_id::{DefId, LOCAL_CRATE}; +use rustc_span::{Symbol, sym}; use crate::diagnostics::DuplicateCanonicalSymbolInCrate; @@ -57,12 +57,15 @@ fn canonical_symbols(tcx: TyCtxt<'_>, _: LocalCrate) -> CanonicalSymbols { // Initialize the collector. let mut canonical_symbols = CanonicalSymbols::new(); - // Collect canonical symbols in this crate. - let crate_items = tcx.hir_crate_items(()); - for id in crate_items.foreign_items() { - observe_item(tcx, &mut canonical_symbols, id); + // Optimization: can this crate even define canonical items? + // (But do not mark `rustc_attrs` as used while doing so) + if tcx.features().enabled_features().contains(&sym::rustc_attrs) { + // Collect canonical symbols in this crate. + let crate_items = tcx.hir_crate_items(()); + for id in crate_items.foreign_items() { + observe_item(tcx, &mut canonical_symbols, id); + } } - canonical_symbols } diff --git a/compiler/rustc_passes/src/diagnostic_items.rs b/compiler/rustc_passes/src/diagnostic_items.rs index 49e46a05e5ccd..e950a26af91a3 100644 --- a/compiler/rustc_passes/src/diagnostic_items.rs +++ b/compiler/rustc_passes/src/diagnostic_items.rs @@ -13,8 +13,8 @@ use rustc_hir::diagnostic_items::DiagnosticItems; use rustc_hir::{CRATE_OWNER_ID, OwnerId, find_attr}; use rustc_middle::query::{LocalCrate, Providers}; use rustc_middle::ty::TyCtxt; -use rustc_span::Symbol; use rustc_span::def_id::{DefId, LOCAL_CRATE}; +use rustc_span::{Symbol, sym}; use crate::diagnostics::DuplicateDiagnosticItemInCrate; @@ -53,15 +53,19 @@ fn report_duplicate_item( }); } -/// Traverse and collect the diagnostic items in the current +/// Traverse and collect the diagnostic items in the current crate fn diagnostic_items(tcx: TyCtxt<'_>, _: LocalCrate) -> DiagnosticItems { // Initialize the collector. let mut diagnostic_items = DiagnosticItems::default(); - // Collect diagnostic items in this crate. - let crate_items = tcx.hir_crate_items(()); - for id in crate_items.owners().chain(std::iter::once(CRATE_OWNER_ID)) { - observe_item(tcx, &mut diagnostic_items, id); + // Optimization: can this crate even define diagnostic items? + // (But do not mark `rustc_attrs` as used while doing so) + if tcx.features().enabled_features().contains(&sym::rustc_attrs) { + // Collect diagnostic items in this crate. + let crate_items = tcx.hir_crate_items(()); + for id in crate_items.owners().chain(std::iter::once(CRATE_OWNER_ID)) { + observe_item(tcx, &mut diagnostic_items, id); + } } diagnostic_items From 77977f9966b681ff865f810869859bcb2eb13a8e Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Wed, 22 Jul 2026 20:32:58 +0200 Subject: [PATCH 08/63] implement `CovariantUnsafeCell` --- compiler/rustc_hir/src/lang_items.rs | 1 + .../rustc_hir_analysis/src/variance/terms.rs | 1 + compiler/rustc_span/src/symbol.rs | 1 + library/core/src/cell.rs | 3 + .../core/src/cell/covariant_unsafe_cell.rs | 172 ++++++++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 library/core/src/cell/covariant_unsafe_cell.rs diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index c7c37d30dd546..4c8c1b2c86e19 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -224,6 +224,7 @@ language_item_table! { IndexMut, sym::index_mut, index_mut_trait, Target::Trait, GenericRequirement::Exact(1); UnsafeCell, sym::unsafe_cell, unsafe_cell_type, Target::Struct, GenericRequirement::None; + CovariantUnsafeCell, sym::covariant_unsafe_cell, covariant_unsafe_cell_type, Target::Struct, GenericRequirement::Exact(1); UnsafePinned, sym::unsafe_pinned, unsafe_pinned_type, Target::Struct, GenericRequirement::None; VaArgSafe, sym::va_arg_safe, va_arg_safe, Target::Trait, GenericRequirement::None; diff --git a/compiler/rustc_hir_analysis/src/variance/terms.rs b/compiler/rustc_hir_analysis/src/variance/terms.rs index 6faeab4217e37..d22eb01467ebe 100644 --- a/compiler/rustc_hir_analysis/src/variance/terms.rs +++ b/compiler/rustc_hir_analysis/src/variance/terms.rs @@ -111,6 +111,7 @@ fn lang_items(tcx: TyCtxt<'_>) -> Vec<(LocalDefId, Vec)> { let all = [ (lang_items.phantom_data(), vec![ty::Covariant]), (lang_items.unsafe_cell_type(), vec![ty::Invariant]), + (lang_items.covariant_unsafe_cell_type(), vec![ty::Covariant]), ]; all.into_iter() // iterating over (Option, Variance) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 3b44015b4f452..27c6bc0f4b6d7 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -737,6 +737,7 @@ symbols! { cosf64, cosf128, count, + covariant_unsafe_cell, coverage, coverage_attribute, cr, diff --git a/library/core/src/cell.rs b/library/core/src/cell.rs index 1b8ec2a91478a..7051ab22581f6 100644 --- a/library/core/src/cell.rs +++ b/library/core/src/cell.rs @@ -259,9 +259,12 @@ use crate::pin::PinCoerceUnsized; use crate::ptr::{self, NonNull}; use crate::range; +mod covariant_unsafe_cell; mod lazy; mod once; +#[unstable(feature = "covariant_unsafe_cell", issue = "159735")] +pub use covariant_unsafe_cell::CovariantUnsafeCell; #[stable(feature = "lazy_cell", since = "1.80.0")] pub use lazy::LazyCell; #[stable(feature = "once_cell", since = "1.70.0")] diff --git a/library/core/src/cell/covariant_unsafe_cell.rs b/library/core/src/cell/covariant_unsafe_cell.rs new file mode 100644 index 0000000000000..11ac9b58bbb90 --- /dev/null +++ b/library/core/src/cell/covariant_unsafe_cell.rs @@ -0,0 +1,172 @@ +use crate::cell::UnsafeCell; +use crate::fmt; +use crate::ops::CoerceUnsized; +use crate::ptr::{self, NonNull}; + +/// **Co**variant version of [`UnsafeCell`]. +#[unstable(feature = "covariant_unsafe_cell", issue = "159735")] +#[repr(transparent)] +#[rustc_pub_transparent] +// Implementation note: +// +// We could make `CovariantUnsafeCell` be the canonical lang item and make `UnsafeCell` a wrapper +// over it, with `PhantomData<*mut T>`. That would however be a huge compiler change, without clear +// benefit. +// +// As such, `CovariantUnsafeCell` is wrapping `UnsafeCell` instead. It is a lang-item only to +// hardcode its variance to be **co**variant in `T`, even though it is wrapping `UnsafeCell` which +// is **in**variant in `T`. +#[lang = "covariant_unsafe_cell"] +pub struct CovariantUnsafeCell(UnsafeCell); + +#[unstable(feature = "covariant_unsafe_cell", issue = "159735")] +impl !Sync for CovariantUnsafeCell {} + +impl CovariantUnsafeCell { + /// Constructs a new instance of `CovariantUnsafeCell` which will wrap the specified value. + /// + /// All access to the inner value through `&CovariantUnsafeCell` requires `unsafe` code. + /// + /// # Examples + /// + /// ``` + /// #![feature(covariant_unsafe_cell)] + /// use std::cell::CovariantUnsafeCell; + /// + /// let uc = CovariantUnsafeCell::new(5); + /// ``` + #[unstable(feature = "covariant_unsafe_cell", issue = "159735")] + #[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")] + #[inline(always)] + pub const fn new(value: T) -> CovariantUnsafeCell { + CovariantUnsafeCell(UnsafeCell::new(value)) + } + + /// Unwraps the value, consuming the cell. + /// + /// # Examples + /// + /// ``` + /// #![feature(covariant_unsafe_cell)] + /// use std::cell::CovariantUnsafeCell; + /// + /// let uc = CovariantUnsafeCell::new(5); + /// + /// let five = uc.into_inner(); + /// ``` + #[inline(always)] + #[unstable(feature = "covariant_unsafe_cell", issue = "159735")] + #[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")] + pub const fn into_inner(self) -> T { + self.0.into_inner() + } +} + +impl CovariantUnsafeCell { + /// Gets a mutable non-null pointer to the wrapped value. + /// + /// This can be cast to a pointer of any kind. When creating (shared or mutable) references, you + /// must uphold the aliasing rules; see [the `UnsafeCell` type-level docs] for more discussion + /// and caveats. + /// + /// [the `UnsafeCell` type-level docs]: super::UnsafeCell#aliasing-rules + /// + /// # Examples + /// + /// ``` + /// #![feature(covariant_unsafe_cell)] + /// use std::cell::CovariantUnsafeCell; + /// use std::ptr::NonNull; + /// + /// let uc = CovariantUnsafeCell::new(5); + /// + /// let ptr: NonNull = uc.get(); + /// ``` + #[inline(always)] + #[rustc_as_ptr] + #[rustc_should_not_be_called_on_const_items] + #[unstable(feature = "covariant_unsafe_cell", issue = "159735")] + #[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")] + pub const fn get(&self) -> NonNull { + // We can just cast the pointer from `CovariantUnsafeCell` to `T` because of + // #[repr(transparent)]. + // + // Note that this is also known to be allowed for user code as per + // `#[rustc_pub_transparent]`. + // SAFETY: the pointer is not null, as it comes from a reference + unsafe { NonNull::new_unchecked(ptr::from_ref(self).cast_mut() as *mut T) } + } + + /// Returns a mutable reference to the underlying data. + /// + /// This call borrows the `CovariantUnsafeCell` mutably (at compile-time) which guarantees that + /// we possess the only reference. + /// + /// # Examples + /// + /// ``` + /// #![feature(covariant_unsafe_cell)] + /// use std::cell::CovariantUnsafeCell; + /// + /// let mut c = CovariantUnsafeCell::new(5); + /// *c.get_mut() += 1; + /// + /// assert_eq!(*c.get_mut(), 6); + /// ``` + #[inline(always)] + #[unstable(feature = "covariant_unsafe_cell", issue = "159735")] + #[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")] + pub const fn get_mut(&mut self) -> &mut T { + self.0.get_mut() + } + + /// Gets a mutable pointer to the wrapped value. + /// The difference from [`get`] is that this function accepts a raw pointer, + /// which is useful to avoid the creation of temporary references. + /// + /// This can be cast to a pointer of any kind. When creating (shared or mutable) references, you + /// must uphold the aliasing rules; see [the `UnsafeCell` type-level docs] for more discussion + /// and caveats. + /// + /// [`get`]: CovariantUnsafeCell::get() + /// + /// # Examples + /// + /// Gradual initialization of an `CovariantUnsafeCell` requires `raw_get`, as + /// calling `get` would require creating a reference to uninitialized data: + /// + /// ``` + /// #![feature(covariant_unsafe_cell)] + /// use std::cell::CovariantUnsafeCell; + /// use std::mem::MaybeUninit; + /// + /// let m = MaybeUninit::>::uninit(); + /// unsafe { CovariantUnsafeCell::raw_get(m.as_ptr()).write(5); } + /// // avoid below which references to uninitialized data + /// // unsafe { CovariantUnsafeCell::get(&*m.as_ptr()).write(5); } + /// let uc = unsafe { m.assume_init() }; + /// + /// assert_eq!(uc.into_inner(), 5); + /// ``` + #[inline(always)] + #[unstable(feature = "covariant_unsafe_cell", issue = "159735")] + #[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")] + pub const fn raw_get(this: *const Self) -> *mut T { + // We can just cast the pointer from `UnsafeCell` to `T` because of + // #[repr(transparent)]. + // + // Note that this is also known to be allowed for user code as per + // `#[rustc_pub_transparent]`. + this as *const T as *mut T + } +} + +#[unstable(feature = "coerce_unsized", issue = "18598")] +impl, U> CoerceUnsized> for CovariantUnsafeCell {} + +#[unstable(feature = "covariant_unsafe_cell", issue = "159735")] +impl fmt::Debug for CovariantUnsafeCell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CovariantUnsafeCell").finish_non_exhaustive() + } +} From c37239a9d8c3272792edbbbed212cd1e64f078c6 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Wed, 22 Jul 2026 21:21:23 +0200 Subject: [PATCH 09/63] add a test checking that `CovariantUnsafeCell` is covariant --- library/core/src/cell/covariant_unsafe_cell.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/library/core/src/cell/covariant_unsafe_cell.rs b/library/core/src/cell/covariant_unsafe_cell.rs index 11ac9b58bbb90..3876cc404cd2e 100644 --- a/library/core/src/cell/covariant_unsafe_cell.rs +++ b/library/core/src/cell/covariant_unsafe_cell.rs @@ -170,3 +170,14 @@ impl fmt::Debug for CovariantUnsafeCell { f.debug_struct("CovariantUnsafeCell").finish_non_exhaustive() } } + +#[cfg(test)] +mod tests { + use super::*; + + fn _covarience<'short, 'long: 'short>( + x: CovariantUnsafeCell<&'long ()>, + ) -> CovariantUnsafeCell<&'short ()> { + x + } +} From e3712ee699b86af81c1d610ed2cd01dd16a2ffba Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Jul 2026 22:20:46 +0200 Subject: [PATCH 10/63] x test cargo-miri: run all subcrate tests as well also add a test for cross-crate static initializers --- src/bootstrap/src/core/build_steps/test.rs | 4 ++ .../exported-symbol-dep/src/lib.rs | 46 +++++++++++++++++++ .../exported-symbol/src/lib.rs | 1 + src/tools/miri/test-cargo-miri/src/main.rs | 12 +++-- 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 9ebe3191db40f..765a51c3901a0 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -839,6 +839,10 @@ impl Step for CargoMiri { SourceType::Submodule, &[], ); + // Run subcrate tests as well. + cargo.arg("--workspace"); + // Some tests need isolation disabled. + cargo.env("MIRIFLAGS", "-Zmiri-disable-isolation"); // If we are testing stage 2+ cargo miri, make sure that it works with the in-tree cargo. // We want to do this *somewhere* to ensure that Miri + nightly cargo actually works. diff --git a/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs b/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs index 5b8a314ae7324..cd2a1e902fc2b 100644 --- a/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs +++ b/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs @@ -11,3 +11,49 @@ impl AssocFn { -123456 } } + +// Also check static constructors in dependencies are run. + +#[rustfmt::skip] +#[macro_export] +macro_rules! ctor { + ($ident:ident = $ctor:ident) => { + #[cfg_attr( + all(any( + target_os = "linux", + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "none", + target_family = "wasm", + )), + link_section = ".init_array" + )] + #[cfg_attr(windows, link_section = ".CRT$XCU")] + #[cfg_attr( + any(target_os = "macos", target_os = "ios"), + // We do not set the `mod_init_funcs` flag here since ctor/inventory also do not do + // that. See . + link_section = "__DATA,__mod_init_func" + )] + #[used] + static $ident: unsafe extern "C" fn() = $ctor; + }; +} + +static mut INITIALIZED: bool = false; + +unsafe extern "C" fn ctor() { + unsafe { INITIALIZED = true }; +} + +pub fn check_initialized() { + assert!(unsafe { INITIALIZED }); +} + +ctor! { CTOR = ctor } diff --git a/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs b/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs index de55eb2a1a5a0..ceb8a3633c5e1 100644 --- a/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs +++ b/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs @@ -1 +1,2 @@ extern crate exported_symbol_dep; +pub use exported_symbol_dep::check_initialized; diff --git a/src/tools/miri/test-cargo-miri/src/main.rs b/src/tools/miri/test-cargo-miri/src/main.rs index 00a239a9161a1..1568da43afe30 100644 --- a/src/tools/miri/test-cargo-miri/src/main.rs +++ b/src/tools/miri/test-cargo-miri/src/main.rs @@ -67,6 +67,10 @@ fn main() { mod test { use byteorder_2::{BigEndian, ByteOrder}; + extern crate cargo_miri_test; + extern crate exported_symbol; + extern crate issue_rust_86261; + // Make sure in-crate tests with dev-dependencies work #[test] fn dev_dependency() { @@ -75,9 +79,6 @@ mod test { #[test] fn exported_symbol() { - extern crate cargo_miri_test; - extern crate exported_symbol; - extern crate issue_rust_86261; // Test calling exported symbols in (transitive) dependencies. // Repeat calls to make sure the `Instance` cache is not broken. for _ in 0..3 { @@ -95,4 +96,9 @@ mod test { unsafe { no_mangle_generic() } } } + + #[test] + fn static_initializer_in_dep() { + exported_symbol::check_initialized(); + } } From b7973ab76afb60c2c2f81e8cc2e4dc8176b7c077 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Jul 2026 18:47:27 +0200 Subject: [PATCH 11/63] clarify some potentially-confusing parts of the UnsafeCell docs --- library/core/src/cell.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/library/core/src/cell.rs b/library/core/src/cell.rs index 1b8ec2a91478a..7f2917e36d7a5 100644 --- a/library/core/src/cell.rs +++ b/library/core/src/cell.rs @@ -2150,8 +2150,9 @@ impl fmt::Display for RefMut<'_, T> { /// use `UnsafeCell` to wrap their data. /// /// Note that only the immutability guarantee for shared references is affected by `UnsafeCell`. The -/// uniqueness guarantee for mutable references is unaffected. There is *no* legal way to obtain -/// aliasing `&mut`, not even with `UnsafeCell`. +/// uniqueness guarantee for mutable references is unaffected. As explained below, for the duration +/// of the lifetime of an `&mut`, no other reference may exist and no pointer may be used to access +/// that memory; this applies even with `UnsafeCell`. /// /// `UnsafeCell` does nothing to avoid data races; they are still undefined behavior. If multiple /// threads have access to the same `UnsafeCell`, they must follow the usual rules of the @@ -2171,11 +2172,13 @@ impl fmt::Display for RefMut<'_, T> { /// /// - If you create a safe reference with lifetime `'a` (either a `&T` or `&mut T` reference), then /// you must not access the data in any way that contradicts that reference for the remainder of -/// `'a`. For example, this means that if you take the `*mut T` from an `UnsafeCell` and cast it -/// to a `&T`, then the data in `T` must remain immutable (modulo any `UnsafeCell` data found -/// within `T`, of course) until that reference's lifetime expires. Similarly, if you create a -/// `&mut T` reference, then you must not access the data within the -/// `UnsafeCell` until that reference expires. +/// `'a`, and you must not create any contradicting references. For example, this means that if +/// you take the `*mut T` from an `UnsafeCell` and cast it to a `&T`, then the data in `T` must +/// remain immutable (modulo any `UnsafeCell` data found within `T`, of course) until that +/// reference's lifetime expires, and no `&mut` reference to this data may be created. Similarly, +/// if you create a `&mut T` reference, then you must not access the data within the `UnsafeCell` +/// with any other pointer/reference until that reference expires, and no reference of any kind +/// may be created. /// /// - For both `&T` without `UnsafeCell<_>` and `&mut T`, you must also not deallocate the data /// until the reference expires. As a special exception, given a `&T`, any part of it that is @@ -2200,7 +2203,7 @@ impl fmt::Display for RefMut<'_, T> { /// Note that whilst mutating the contents of a `&UnsafeCell` (even while other /// `&UnsafeCell` references alias the cell) is /// ok (provided you enforce the above invariants some other way), it is still undefined behavior -/// to have multiple `&mut UnsafeCell` aliases. That is, `UnsafeCell` is a wrapper +/// to have aliasing `&mut UnsafeCell` (or aliasing `&mut` of *any* type). That is, `UnsafeCell` is a wrapper /// designed to have a special interaction with _shared_ accesses (_i.e._, through an /// `&UnsafeCell<_>` reference); there is no magic whatsoever when dealing with _exclusive_ /// accesses (_e.g._, through a `&mut UnsafeCell<_>`): neither the cell nor the wrapped value From 678de5f12f12ce9312a1b69430cff055e865321f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Jul 2026 18:54:23 +0200 Subject: [PATCH 12/63] allow accessing the contents of UnsafeCell without going through get / raw_get --- library/core/src/cell.rs | 39 +++++++++++++-------------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/library/core/src/cell.rs b/library/core/src/cell.rs index 7f2917e36d7a5..e6a7acd74d9d9 100644 --- a/library/core/src/cell.rs +++ b/library/core/src/cell.rs @@ -2226,36 +2226,20 @@ impl fmt::Display for RefMut<'_, T> { /// order to avoid its interior mutability property from spreading from `T` into the `Outer` type, /// thus this can cause distortions in the type size in these cases. /// -/// Note that the only valid way to obtain a `*mut T` pointer to the contents of a -/// _shared_ `UnsafeCell` is through [`.get()`] or [`.raw_get()`]. A `&T` or `&mut T` reference -/// can then be obtained from that pointer, as long as the aliasing rules outlined above are obeyed. -/// Even though `T` and `UnsafeCell` have the -/// same memory layout, the following is not allowed and undefined behavior: +/// The following examples make use of this guarantee: /// -/// ```rust,compile_fail +/// ```rust /// # use std::cell::UnsafeCell; -/// unsafe fn not_allowed(ptr: &UnsafeCell) -> &mut T { +/// /// # Safety +/// /// The caller must not call `get_mut_unchecked` again (on any alias of `ptr`) for the duration +/// /// of the lifetime of the returned reference. +/// # #[allow(invalid_reference_casting)] // FIXME should the lint really fire here? +/// unsafe fn get_mut_unchecked(ptr: &UnsafeCell) -> &mut T { /// let t = ptr as *const UnsafeCell as *mut T; -/// // This is undefined behavior, because the `*mut T` pointer -/// // was not obtained through `.get()` nor `.raw_get()`: /// unsafe { &mut *t } /// } /// ``` /// -/// Instead, do this: -/// -/// ```rust -/// # use std::cell::UnsafeCell; -/// // Safety: the caller must ensure that there are no references that -/// // point to the *contents* of the `UnsafeCell`. -/// unsafe fn get_mut(ptr: &UnsafeCell) -> &mut T { -/// unsafe { &mut *ptr.get() } -/// } -/// ``` -/// -/// Converting in the other direction from a `&mut T` -/// to an `&UnsafeCell` is allowed: -/// /// ```rust /// # use std::cell::UnsafeCell; /// fn get_shared(ptr: &mut T) -> &UnsafeCell { @@ -2266,7 +2250,6 @@ impl fmt::Display for RefMut<'_, T> { /// ``` /// /// [niche]: https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#niche -/// [`.raw_get()`]: `UnsafeCell::raw_get` /// /// # Examples /// @@ -2428,6 +2411,9 @@ impl UnsafeCell { /// must uphold the aliasing rules; see [the type-level docs][UnsafeCell#aliasing-rules] for /// more discussion and caveats. /// + /// This is equivalent to casting `self` to a raw pointer and then casting that raw + /// pointer to `*mut T`. + /// /// # Examples /// /// ``` @@ -2445,8 +2431,7 @@ impl UnsafeCell { #[rustc_should_not_be_called_on_const_items] pub const fn get(&self) -> *mut T { // We can just cast the pointer from `UnsafeCell` to `T` because of - // #[repr(transparent)]. This exploits std's special status, there is - // no guarantee for user code that this will work in future versions of the compiler! + // #[repr(transparent)]. self as *const UnsafeCell as *const T as *mut T } @@ -2480,6 +2465,8 @@ impl UnsafeCell { /// must uphold the aliasing rules; see [the type-level docs][UnsafeCell#aliasing-rules] for /// more discussion and caveats. /// + /// This is equivalent to casting `this` to `*mut T`. + /// /// [`get`]: UnsafeCell::get() /// /// # Examples From 8242b333dab98c8f60b08be6dd397cbd0d3bf4d0 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Jul 2026 16:18:51 +0200 Subject: [PATCH 13/63] document #[global_allocator] constraints --- library/alloc/src/alloc.rs | 85 ++++++++++++++++ library/core/src/alloc/global.rs | 97 +++++++++++++------ library/std/src/alloc.rs | 5 + src/tools/miri/tests/pass/global_allocator.rs | 13 ++- 4 files changed, 169 insertions(+), 31 deletions(-) diff --git a/library/alloc/src/alloc.rs b/library/alloc/src/alloc.rs index 8560a644f207b..49bf941984af5 100644 --- a/library/alloc/src/alloc.rs +++ b/library/alloc/src/alloc.rs @@ -64,6 +64,28 @@ pub struct Global; /// of the allocator registered with the `#[global_allocator]` attribute /// if there is one, or the `std` crate’s default. /// +/// Note, however, that invoking this function is *not* equivalent to invoking the underlying +/// [`GlobalAlloc::alloc`] method of the registered allocator directly. Users of this function +/// cannot assume anything about what the allocator does, other than the documented requirements. +/// This means: +/// +/// - This function may non-deterministically entirely skip the underlying allocator, e.g. if the +/// compiler can show that this allocation can be replaced by a stack variable. The compiler may +/// also merge multiple allocation operations into one, as long as it can also adjust all +/// corresponding deallocation operations accordingly. +/// - An allocation created by invoking this function has exactly the size and minimum alignment +/// defined by `layout`, even if the underlying allocator makes stronger promises. +/// - The allocation can only be freed by invoking [`dealloc`] or [`realloc`]. In particular, +/// passing a pointer to such an allocation directly to the underlying method on [`GlobalAlloc`] is +/// not permitted. Until one of those functions is called, it is undefined behavior to access the +/// memory that backs this allocation with any pointer not derived from the return value of this +/// function (e.g., with internal pointers the allocator might keep around). +/// - This function de-initializes the contents of the allocation before handing it to the user. So even +/// if you control the underlying allocator and know that it explicitly initialized this memory, +/// you cannot rely on it being initialized. +/// +/// Users of this function have to consider that in the future, allocators may be allowed to unwind. +/// /// This function is expected to be deprecated in favor of the `allocate` method /// of the [`Global`] type when it and the [`Allocator`] trait become stable. /// @@ -109,6 +131,24 @@ pub unsafe fn alloc(layout: Layout) -> *mut u8 { /// of the allocator registered with the `#[global_allocator]` attribute /// if there is one, or the `std` crate’s default. /// +/// Note, however, that invoking this function is *not* equivalent to invoking the underlying +/// [`GlobalAlloc::dealloc`] method of the registered allocator directly. Users of this function +/// cannot assume anything about what the allocator does, other than the documented requirements. +/// This means: +/// +/// - This function may non-deterministically entirely skip the underlying allocator, e.g. if the +/// compiler can show that this allocation can be replaced by a stack variable. The compiler may +/// also merge multiple allocation operations into one, as long as it can also adjust all +/// corresponding deallocation operations accordingly. +/// - The pointer passed to this function must have been obtained by invoking [`alloc`], +/// [`alloc_zeroed`], or [`realloc`]. In particular, passing a pointer returned by the underlying +/// methods on [`GlobalAlloc`] is not permitted. +/// - This function de-initializes the contents of the allocation before handing it to the allocator. +/// So even if you know that the program previously initialized that memory, the allocator cannot +/// rely on it being initialized. +/// +/// Users of this function have to consider that in the future, allocators may be allowed to unwind. +/// /// This function is expected to be deprecated in favor of the `deallocate` method /// of the [`Global`] type when it and the [`Allocator`] trait become stable. /// @@ -135,6 +175,32 @@ unsafe fn dealloc_nonnull(ptr: NonNull, layout: Layout) { /// of the allocator registered with the `#[global_allocator]` attribute /// if there is one, or the `std` crate’s default. /// +/// Note, however, that invoking this function is *not* equivalent to invoking the underlying +/// [`GlobalAlloc::realloc`] method of the registered allocator directly. Users of this function +/// cannot assume anything about what the allocator does, other than the documented requirements. +/// This means: +/// +/// - This function may non-deterministically entirely skip the underlying allocator, e.g. if the +/// compiler can show that this allocation can be replaced by a stack variable. The compiler may +/// also merge multiple allocation operations into one, as long as it can also adjust all +/// corresponding deallocation operations accordingly. +/// - The pointer passed to this function must have been obtained by invoking [`alloc`], +/// [`alloc_zeroed`], or [`realloc`]. In particular, passing a pointer returned by the underlying +/// methods on [`GlobalAlloc`] is not permitted. +/// - An allocation created by invoking this function has exactly the size and minimum alignment +/// defined by `layout`, even if the underlying allocator makes stronger promises. +/// - The allocation can only be freed by invoking [`dealloc`] or [`realloc`]. In particular, +/// passing a pointer to such an allocation directly to the underlying method on [`GlobalAlloc`] is +/// not permitted. Until one of those functions is called, it is undefined behavior to access the +/// memory that backs this allocation with any pointer not derived from the return value of this +/// function (e.g., with internal pointers the allocator might keep around). +/// - If this grows the allocation, the contents of the grown part of the new allocation allocation +/// are de-initialized by this function before returning. +/// - If this shrinks the allocation, the contents of the removed part of the old allocation are +/// de-initialized by this function before invoking the underlying allocator. +/// +/// Users of this function have to consider that in the future, allocators may be allowed to unwind. +/// /// This function is expected to be deprecated in favor of the `grow` and `shrink` methods /// of the [`Global`] type when it and the [`Allocator`] trait become stable. /// @@ -162,6 +228,25 @@ unsafe fn realloc_nonnull(ptr: NonNull, layout: Layout, new_size: usize) -> /// of the allocator registered with the `#[global_allocator]` attribute /// if there is one, or the `std` crate’s default. /// +/// Note, however, that invoking this function is *not* equivalent to invoking the underlying +/// [`GlobalAlloc::alloc_zeroed`] method of the registered allocator directly. Users of this +/// function cannot assume anything about what the allocator does, other than the documented +/// requirements. This means: +/// +/// - This function may non-deterministically entirely skip the underlying allocator, e.g. if the +/// compiler can show that this allocation can be replaced by a stack variable. The compiler may +/// also merge multiple allocation operations into one, as long as it can also adjust all +/// corresponding deallocation operations accordingly. +/// - The allocation can only be freed by invoking [`dealloc`] or [`realloc`]. In particular, +/// passing a pointer to such an allocation directly to the underlying method on [`GlobalAlloc`] is +/// not permitted. Until one of those functions is called, it is undefined behavior to access the +/// memory that backs this allocation with any pointer not derived from the return value of this +/// function (e.g., with internal pointers the allocator might keep around). +/// - An allocation created by invoking this function has exactly the size and minimum alignment +/// defined by `layout`, even if the underlying allocator makes stronger promises. +/// +/// Users of this function have to consider that in the future, allocators may be allowed to unwind. +/// /// This function is expected to be deprecated in favor of the `allocate_zeroed` method /// of the [`Global`] type when it and the [`Allocator`] trait become stable. /// diff --git a/library/core/src/alloc/global.rs b/library/core/src/alloc/global.rs index 44a8ab6e54196..0036b7a55c8ce 100644 --- a/library/core/src/alloc/global.rs +++ b/library/core/src/alloc/global.rs @@ -19,7 +19,6 @@ use crate::{cmp, ptr}; /// method such as `dealloc` or by being /// passed to a reallocation method that returns a non-null pointer. /// -/// /// # Example /// /// ```standalone_crate @@ -85,39 +84,83 @@ use crate::{cmp, ptr}; /// } /// ``` /// +/// # The `#[global_allocator]` attribute +/// +/// As the example above demonstrates, the `#[global_allocator]` attribute can be used to register a +/// concrete `static` of a type that implements this trait to become *the* global allocator +/// for the current program. That global allocator can be invoked via the functions [`alloc`], +/// [`alloc_zeroed`], [`dealloc`], [`realloc`]). Note, however, that invoking those functions is +/// *not* equivalent to directly invoking the underlying methods on the declared global allocator! +/// Users of the global allocator cannot assume anything about what the allocator does (even if they know which allocator is being used), +/// and implementors of the allocator cannot assume anything about what the program does (even if they know how the allocator is being used). +/// Both can only assume the documented requirements for the respective other party of this contract. +/// This means: +/// +/// - Allocation functions may non-deterministically entirely skip the underlying allocator, e.g. if the +/// compiler can show that this allocation can be replaced by a stack variable. The compiler may +/// also merge multiple allocation operations into one, as long as it can also adjust all +/// corresponding deallocation operations accordingly. +/// - An allocation created by invoking [`alloc`], [`alloc_zeroed`], or [`realloc`] has exactly the +/// size and minimum alignment defined by `layout`, even if the underlying allocator makes +/// stronger promises. +/// - An allocation created by invoking [`alloc`], [`alloc_zeroed`], or [`realloc`] can only be +/// freed by invoking [`dealloc`] or [`realloc`]. In particular, passing a pointer to such an +/// allocation directly to the underlying method on [`GlobalAlloc`] is not permitted. Until one of +/// those functions is called, it is undefined behavior to access the memory that backs this +/// allocation with any pointer not derived from the return value of this function (e.g., with +/// internal pointers the allocator might keep around). +/// - The pointer passed to [`dealloc`] or [`realloc`] must have been obtained by invoking [`alloc`], +/// [`alloc_zeroed`], or [`realloc`]. In particular, passing a pointer returned by the underlying +/// methods on [`GlobalAlloc`] is not permitted. +/// - [`alloc`] de-initializes the contents of the allocation before handing it to the user. So even +/// if you control the underlying allocator and know that it explicitly initialized this memory, +/// you cannot rely on it being initialized. For a [`realloc`] that grows an allocation, this +/// applies to the newly allocated part. +/// - [`dealloc`] de-initializes the contents of the allocation before handing it to the allocator. +/// So even if you know that the program previously initialized that memory, the allocator cannot +/// rely on it being initialized. For a [`realloc`] that shrinks an allocation, this applies to +/// the part being removed. +/// +/// [`alloc`]: ../../std/alloc/fn.alloc.html +/// [`alloc_zeroed`]: ../../std/alloc/fn.alloc_zeroed.html +/// [`dealloc`]: ../../std/alloc/fn.dealloc.html +/// [`realloc`]: ../../std/alloc/fn.realloc.html +/// +/// The first point means that you cannot rely on global allocations actually happening, even if +/// there are explicit global allocations in the source. The optimizer may detect unused global +/// allocations that it can either eliminate entirely or move to the stack and thus never invoke the +/// global allocator. The optimizer may further assume that allocation is infallible, so code that +/// used to fail due to allocator failures may now suddenly work because the optimizer worked around +/// the need for an allocation. More concretely, the following code example is unsound, irrespective +/// of whether your custom allocator allows counting how many allocations have happened. +/// +/// ```rust,ignore (unsound and has placeholders) +/// drop(Box::new(42)); +/// let number_of_heap_allocs = /* call private allocator API */; +/// unsafe { std::hint::assert_unchecked(number_of_heap_allocs > 0); } +/// ``` +/// +/// Note that the optimizations mentioned above are not the only +/// optimization that can be applied. You may generally not rely on global allocations +/// happening if they can be removed without changing program behavior. +/// Whether allocations happen or not is not part of the program behavior, even if it +/// could be detected via an allocator that tracks allocations by printing or otherwise +/// having side effects. +/// /// # Safety /// /// The `GlobalAlloc` trait is an `unsafe` trait for a number of reasons, and /// implementors must ensure that they adhere to these contracts: /// +/// * It is undefined behavior for the allocator to read, write, or deallocate any memory that +/// is *currently allocated*. This memory is owned by the user, the allocator must not touch it. +/// /// * It's undefined behavior if global allocators unwind. This restriction may /// be lifted in the future, but currently a panic from any of these /// functions may lead to memory unsafety. /// -/// * `Layout` queries and calculations in general must be correct. Callers of -/// this trait are allowed to rely on the contracts defined on each method, -/// and implementors must ensure such contracts remain true. -/// -/// * You must not rely on allocations actually happening, even if there are explicit -/// heap allocations in the source. The optimizer may detect unused allocations that it can either -/// eliminate entirely or move to the stack and thus never invoke the allocator. The -/// optimizer may further assume that allocation is infallible, so code that used to fail due -/// to allocator failures may now suddenly work because the optimizer worked around the -/// need for an allocation. More concretely, the following code example is unsound, irrespective -/// of whether your custom allocator allows counting how many allocations have happened. -/// -/// ```rust,ignore (unsound and has placeholders) -/// drop(Box::new(42)); -/// let number_of_heap_allocs = /* call private allocator API */; -/// unsafe { std::hint::assert_unchecked(number_of_heap_allocs > 0); } -/// ``` -/// -/// Note that the optimizations mentioned above are not the only -/// optimization that can be applied. You may generally not rely on heap allocations -/// happening if they can be removed without changing program behavior. -/// Whether allocations happen or not is not part of the program behavior, even if it -/// could be detected via an allocator that tracks allocations by printing or otherwise -/// having side effects. +/// * Callers of this trait are allowed to rely on the contracts defined on each method, and +/// implementors must ensure such contracts remain true. /// /// # Re-entrance /// @@ -133,7 +176,7 @@ use crate::{cmp, ptr}; /// - [`std::thread_local`], /// - [`std::thread::current`], /// - [`std::thread::park`] and [`std::thread::Thread`]'s [`unpark`] method and -/// [`Clone`] implementation. +/// [`Clone`] implementation. /// /// [`std`]: ../../std/index.html /// [`std::sync::Mutex`]: ../../std/sync/struct.Mutex.html @@ -174,7 +217,7 @@ pub unsafe trait GlobalAlloc { /// /// Clients wishing to abort computation in response to an /// allocation error are encouraged to call the [`handle_alloc_error`] function, - /// rather than directly invoking `panic!` or similar. + /// rather than directly invoking `panic!` or similar (but note that both may unwind). /// /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html #[stable(feature = "global_alloc", since = "1.28.0")] diff --git a/library/std/src/alloc.rs b/library/std/src/alloc.rs index 84447c06aaecf..9d39fbf770e91 100644 --- a/library/std/src/alloc.rs +++ b/library/std/src/alloc.rs @@ -53,6 +53,11 @@ //! The `#[global_allocator]` can only be used once in a crate //! or its recursive dependencies. //! +//! The global allocator is invoked via the functions in this module +//! ([`alloc`][crate::alloc::alloc], [`alloc_zeroed`], [`dealloc`], [`realloc`]). Note, however, +//! that invoking those functions is *not* equivalent to directly invoking the underlying methods on +//! the declared global allocator! See the documentation of those functions for details. +//! //! [^system-alloc]: Note that the Rust standard library internals may still //! directly call [`System`] when necessary (for example for the runtime //! support typically required to implement a global allocator, see [re-entrance] on [`GlobalAlloc`] diff --git a/src/tools/miri/tests/pass/global_allocator.rs b/src/tools/miri/tests/pass/global_allocator.rs index 9a40c322b366e..ecb3a5e46e3a4 100644 --- a/src/tools/miri/tests/pass/global_allocator.rs +++ b/src/tools/miri/tests/pass/global_allocator.rs @@ -7,10 +7,12 @@ static ALLOCATOR: Allocator = Allocator; struct Allocator; +const SIZE: usize = 16 * 7; + unsafe impl GlobalAlloc for Allocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { // use specific size to avoid getting triggered by rt - if layout.size() == 123 { + if layout.size() == SIZE { println!("Allocated!") } @@ -18,7 +20,7 @@ unsafe impl GlobalAlloc for Allocator { } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - if layout.size() == 123 { + if layout.size() == SIZE { println!("Deallocated!") } @@ -27,13 +29,16 @@ unsafe impl GlobalAlloc for Allocator { } fn main() { - // Only okay because we explicitly set a global allocator that uses the system allocator! - let l = Layout::from_size_align(123, 1).unwrap(); + // Below we mix using `Global` and `System`. This is undefined behavior that Miri should + // detect, but currently it does not. + + let l = Layout::from_size_align(SIZE, 1).unwrap(); let ptr = Global.allocate(l).unwrap().as_non_null_ptr(); // allocating with Global... unsafe { System.deallocate(ptr, l); } // ... and deallocating with System. + let l = Layout::from_size_align(SIZE, 16).unwrap(); let ptr = System.allocate(l).unwrap().as_non_null_ptr(); // allocating with System... unsafe { Global.deallocate(ptr, l); From a14d50a7b6f516bfaeaf2a764c3e40879a677093 Mon Sep 17 00:00:00 2001 From: AayushMainali-Github Date: Thu, 23 Jul 2026 18:34:36 +0000 Subject: [PATCH 14/63] rustdoc-js: ignore editor temp files in test folder discovery --- src/tools/rustdoc-js/tester.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js index aaf40ceeadf71..9985fa1d7bcd5 100644 --- a/src/tools/rustdoc-js/tester.js +++ b/src/tools/rustdoc-js/tester.js @@ -539,6 +539,22 @@ async function loadSearchJS(doc_folder, resource_suffix) { }; } +/** + * Returns true if `fileName` looks like a proper rustdoc-js test file. + * + * Mirrors compiletest's `is_test` filtering so editor temp/autosave files + * (for example Emacs `.#foo.js`) are not treated as tests. + */ +function isTestFile(fileName) { + if (!fileName.endsWith(".js")) { + return false; + } + + // `.`, `#`, and `~` are common temp-file prefixes. + const invalidPrefixes = [".", "#", "~"]; + return !invalidPrefixes.some(prefix => fileName.startsWith(prefix)); +} + function showHelp() { console.log("rustdoc-js options:"); console.log(" --doc-folder [PATH] : location of the generated doc folder"); @@ -626,7 +642,7 @@ async function main(argv) { } } else if (opts["test_folder"].length !== 0) { for (const file of fs.readdirSync(opts["test_folder"])) { - if (!file.endsWith(".js")) { + if (!isTestFile(file)) { continue; } process.stdout.write(`Testing ${file} ... `); From af565829d7807db0b2b6ad629e2cbb325929a5cb Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 24 Jul 2026 12:55:35 +1000 Subject: [PATCH 15/63] Avoid `#[target_features]` The string `#[target_features]` is used in various error messages as well as the compiler's code. But there is no such attribute, which I found very confusing. I think it means "one or more target features", e.g. covering both `#[target_feature(foo)]` and `#[target_feature(foo, bar, baz)]`. We already use `#[target_feature(..)]` for that meaning in several places. So this commit changes all `#[target_features]` occurrences to `#[target_feature(..)]`. It also changes a few `#[target_feature]` (no plural) occurrences to `#[target_feature(..)]` for consistency with other mentions nearby in error messages. --- compiler/rustc_hir/src/hir.rs | 2 +- compiler/rustc_middle/src/ty/error.rs | 3 +- compiler/rustc_middle/src/ty/print/pretty.rs | 2 +- compiler/rustc_target/src/target_features.rs | 2 +- .../src/error_reporting/infer/mod.rs | 36 +++++++-------- .../error_reporting/infer/note_and_explain.rs | 4 +- .../traits/fulfillment_errors.rs | 2 +- .../fn-exception-target-features.stderr | 4 +- ...e-effective-target-features.default.stderr | 2 +- ...e-effective-target-features.feature.stderr | 2 +- .../rfc-2396-target_feature-11/fn-ptr.stderr | 16 +++---- .../rfc-2396-target_feature-11/fn-traits.rs | 8 ++-- .../fn-traits.stderr | 44 +++++++++---------- .../trait-impl.stderr | 2 +- .../target-feature/invalid-attribute.stderr | 2 +- 15 files changed, 66 insertions(+), 65 deletions(-) diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 2e272fa3af9d0..28200bc3f4712 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -4769,7 +4769,7 @@ pub enum RestrictionKind<'hir> { /// explicitly to allow unsafe operations. #[derive(Copy, Clone, Debug, StableHash, PartialEq, Eq)] pub enum HeaderSafety { - /// A safe function annotated with `#[target_features]`. + /// A safe function annotated with `#[target_feature(..)]`. /// The type system treats this function as an unsafe function, /// but safety checking will check this enum to treat it as safe /// and allowing calling other safe target feature functions with diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs index 83670daf909fa..a16352e45564e 100644 --- a/compiler/rustc_middle/src/ty/error.rs +++ b/compiler/rustc_middle/src/ty/error.rs @@ -129,7 +129,8 @@ impl<'tcx> TypeError<'tcx> { } TypeError::IntrinsicCast => "cannot coerce intrinsics to function pointers".into(), TypeError::TargetFeatureCast(_) => { - "cannot coerce functions with `#[target_feature]` to safe function pointers".into() + "cannot coerce functions with `#[target_feature(..)]` to safe function pointers" + .into() } } } diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index 3c7e3b63ee9b5..3614a67b71045 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -789,7 +789,7 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { let mut sig = self.tcx().fn_sig(def_id).instantiate(self.tcx(), args).skip_norm_wip(); if self.tcx().codegen_fn_attrs(def_id).safe_target_features { - write!(self, "#[target_features] ")?; + write!(self, "#[target_feature(..)] ")?; sig = sig.map_bound(|mut sig| { sig.fn_sig_kind = sig.fn_sig_kind.set_safety(hir::Safety::Safe); sig diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index fc25849b2d602..3e07621a20d55 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -1219,7 +1219,7 @@ impl Target { /// These features are checked against the target features reported by LLVM based on /// `-Ctarget-cpu` and `-Ctarget-features`. Constraint violations result in a warning. /// - /// We also check features enabled via `#[target_features]` (and here, constraint violations + /// We also check features enabled via `#[target_feature(..)]` (and here, constraint violations /// emit a hard error), including features enabled indirectly via implications -- but if LLVM /// considers more features to be implied than we do, that could bypass this check! pub fn abi_required_features(&self) -> FeatureConstraints { diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs index 69ef6c8d2a5c4..0af41423b846a 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -773,17 +773,17 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let (lt1, sig1) = get_lifetimes(sig1); let (lt2, sig2) = get_lifetimes(sig2); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T let mut values = (DiagStyledString::normal("".to_string()), DiagStyledString::normal("".to_string())); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^^^^^^^^^^^^^^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^^^^^^^^^^^^^^^^^^^ let fn_item_prefix_and_safety = |fn_def, sig: ty::FnSig<'_>| match fn_def { None => ("", sig.safety().prefix_str()), Some((did, _)) => { if self.tcx.codegen_fn_attrs(did).safe_target_features { - ("#[target_features] ", "") + ("#[target_feature(..)] ", "") } else { ("", sig.safety().prefix_str()) } @@ -794,19 +794,19 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { values.0.push(prefix1, prefix1 != prefix2); values.1.push(prefix2, prefix1 != prefix2); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^^^^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^^^^^^ let lifetime_diff = lt1 != lt2; values.0.push(lt1, lifetime_diff); values.1.push(lt2, lifetime_diff); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^^^^ values.0.push(safety1, safety1 != safety2); values.1.push(safety2, safety1 != safety2); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^^^^^^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^^^^^^^^ if sig1.abi() != ExternAbi::Rust { values.0.push(format!("extern {} ", sig1.abi()), sig1.abi() != sig2.abi()); } @@ -814,13 +814,13 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { values.1.push(format!("extern {} ", sig2.abi()), sig1.abi() != sig2.abi()); } - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^ values.0.push_normal("fn("); values.1.push_normal("fn("); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^^^ let len1 = sig1.inputs().len(); let len2 = sig2.inputs().len(); let splatted_arg_index1 = sig1.splatted().map(usize::from); @@ -868,13 +868,13 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { values.1.push("...", !sig1.c_variadic()); } - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^ values.0.push_normal(")"); values.1.push_normal(")"); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^^^^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^^^^^^ let output1 = sig1.output(); let output2 = sig2.output(); let (x1, x2) = self.cmp(output1, output2); diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs index f93d28c29cdfa..7fcc53c2b4348 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs @@ -621,9 +621,9 @@ impl Trait for X { TypeError::TargetFeatureCast(def_id) => { let target_spans = find_attr!(tcx, def_id, TargetFeature{attr_span: span, was_forced: false, ..} => *span); diag.note( - "functions with `#[target_feature]` can only be coerced to `unsafe` function pointers" + "functions with `#[target_feature(..)]` can only be coerced to `unsafe` function pointers" ); - diag.span_labels(target_spans, "`#[target_feature]` added here"); + diag.span_labels(target_spans, "`#[target_feature(..)]` added here"); } _ => {} } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index b98c3fab5bcb5..8af061168a865 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -555,7 +555,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { }; if is_fn_trait && is_target_feature_fn { err.note( - "`#[target_feature]` functions do not implement the `Fn` traits", + "`#[target_feature(..)]` functions do not implement the `Fn` traits", ); err.note( "try casting the function to a `fn` pointer or wrapping it in a closure", diff --git a/tests/ui/async-await/async-closures/fn-exception-target-features.stderr b/tests/ui/async-await/async-closures/fn-exception-target-features.stderr index f0846bfdb1250..62b0c41f4ebc5 100644 --- a/tests/ui/async-await/async-closures/fn-exception-target-features.stderr +++ b/tests/ui/async-await/async-closures/fn-exception-target-features.stderr @@ -1,4 +1,4 @@ -error[E0277]: the trait bound `#[target_features] fn() -> Pin + 'static)>> {target_feature}: AsyncFn()` is not satisfied +error[E0277]: the trait bound `#[target_feature(..)] fn() -> Pin + 'static)>> {target_feature}: AsyncFn()` is not satisfied --> $DIR/fn-exception-target-features.rs:15:10 | LL | test(target_feature); @@ -6,7 +6,7 @@ LL | test(target_feature); | | | required by a bound introduced by this call | - = help: the trait `AsyncFn()` is not implemented for fn item `#[target_features] fn() -> Pin + 'static)>> {target_feature}` + = help: the trait `AsyncFn()` is not implemented for fn item `#[target_feature(..)] fn() -> Pin + 'static)>> {target_feature}` note: required by a bound in `test` --> $DIR/fn-exception-target-features.rs:12:17 | diff --git a/tests/ui/feature-gates/feature-gate-effective-target-features.default.stderr b/tests/ui/feature-gates/feature-gate-effective-target-features.default.stderr index e699c5d77b42c..e503e417f61cf 100644 --- a/tests/ui/feature-gates/feature-gate-effective-target-features.default.stderr +++ b/tests/ui/feature-gates/feature-gate-effective-target-features.default.stderr @@ -29,7 +29,7 @@ note: type in trait LL | fn foo(&self); | ^^^^^^^^^^^^^^ = note: expected signature `fn(&Bar2)` - found signature `#[target_features] fn(&Bar2)` + found signature `#[target_feature(..)] fn(&Bar2)` error: aborting due to 3 previous errors diff --git a/tests/ui/feature-gates/feature-gate-effective-target-features.feature.stderr b/tests/ui/feature-gates/feature-gate-effective-target-features.feature.stderr index bf9f5d73e0573..06090cbbef6fe 100644 --- a/tests/ui/feature-gates/feature-gate-effective-target-features.feature.stderr +++ b/tests/ui/feature-gates/feature-gate-effective-target-features.feature.stderr @@ -32,7 +32,7 @@ note: type in trait LL | fn foo(&self); | ^^^^^^^^^^^^^^ = note: expected signature `fn(&Bar2)` - found signature `#[target_features] fn(&Bar2)` + found signature `#[target_feature(..)] fn(&Bar2)` error: aborting due to 2 previous errors; 1 warning emitted diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/fn-ptr.stderr b/tests/ui/rfcs/rfc-2396-target_feature-11/fn-ptr.stderr index 5bfbc236bc4e3..47bfc68f43a45 100644 --- a/tests/ui/rfcs/rfc-2396-target_feature-11/fn-ptr.stderr +++ b/tests/ui/rfcs/rfc-2396-target_feature-11/fn-ptr.stderr @@ -2,31 +2,31 @@ error[E0308]: mismatched types --> $DIR/fn-ptr.rs:12:21 | LL | #[target_feature(enable = "avx")] - | --------------------------------- `#[target_feature]` added here + | --------------------------------- `#[target_feature(..)]` added here ... LL | let foo: fn() = foo_avx; - | ---- ^^^^^^^ cannot coerce functions with `#[target_feature]` to safe function pointers + | ---- ^^^^^^^ cannot coerce functions with `#[target_feature(..)]` to safe function pointers | | | expected due to this | = note: expected fn pointer `fn()` - found fn item `#[target_features] fn() {foo_avx}` - = note: functions with `#[target_feature]` can only be coerced to `unsafe` function pointers + found fn item `#[target_feature(..)] fn() {foo_avx}` + = note: functions with `#[target_feature(..)]` can only be coerced to `unsafe` function pointers error[E0308]: mismatched types --> $DIR/fn-ptr.rs:21:21 | LL | #[target_feature(enable = "sse2")] - | ---------------------------------- `#[target_feature]` added here + | ---------------------------------- `#[target_feature(..)]` added here ... LL | let foo: fn() = foo; - | ---- ^^^ cannot coerce functions with `#[target_feature]` to safe function pointers + | ---- ^^^ cannot coerce functions with `#[target_feature(..)]` to safe function pointers | | | expected due to this | = note: expected fn pointer `fn()` - found fn item `#[target_features] fn() {foo}` - = note: functions with `#[target_feature]` can only be coerced to `unsafe` function pointers + found fn item `#[target_feature(..)] fn() {foo}` + = note: functions with `#[target_feature(..)]` can only be coerced to `unsafe` function pointers error: aborting due to 2 previous errors diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.rs b/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.rs index b7eaabdf6ad3f..778a3fb7bbbd7 100644 --- a/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.rs +++ b/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.rs @@ -26,10 +26,10 @@ fn call_once_i32(f: impl FnOnce(i32)) { } fn main() { - call(foo); //~ ERROR expected an `Fn()` closure, found `#[target_features] fn() {foo}` - call_mut(foo); //~ ERROR expected an `FnMut()` closure, found `#[target_features] fn() {foo}` - call_once(foo); //~ ERROR expected an `FnOnce()` closure, found `#[target_features] fn() {foo}` - call_once_i32(bar); //~ ERROR expected an `FnOnce(i32)` closure, found `#[target_features] fn(i32) {bar}` + call(foo); //~ ERROR expected an `Fn()` closure, found `#[target_feature(..)] fn() {foo}` + call_mut(foo); //~ ERROR expected an `FnMut()` closure, found `#[target_feature(..)] fn() {foo}` + call_once(foo); //~ ERROR expected an `FnOnce()` closure, found `#[target_feature(..)] fn() {foo}` + call_once_i32(bar); //~ ERROR expected an `FnOnce(i32)` closure, found `#[target_feature(..)] fn(i32) {bar}` call(foo_unsafe); //~^ ERROR expected an `Fn()` closure, found `unsafe fn() {foo_unsafe}` diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.stderr b/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.stderr index 5333307a59280..43744636be196 100644 --- a/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.stderr +++ b/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.stderr @@ -1,14 +1,14 @@ -error[E0277]: expected an `Fn()` closure, found `#[target_features] fn() {foo}` +error[E0277]: expected an `Fn()` closure, found `#[target_feature(..)] fn() {foo}` --> $DIR/fn-traits.rs:29:10 | LL | call(foo); - | ---- ^^^ expected an `Fn()` closure, found `#[target_features] fn() {foo}` + | ---- ^^^ expected an `Fn()` closure, found `#[target_feature(..)] fn() {foo}` | | | required by a bound introduced by this call | - = help: the trait `Fn()` is not implemented for fn item `#[target_features] fn() {foo}` - = note: wrap the `#[target_features] fn() {foo}` in a closure with no arguments: `|| { /* code */ }` - = note: `#[target_feature]` functions do not implement the `Fn` traits + = help: the trait `Fn()` is not implemented for fn item `#[target_feature(..)] fn() {foo}` + = note: wrap the `#[target_feature(..)] fn() {foo}` in a closure with no arguments: `|| { /* code */ }` + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call` --> $DIR/fn-traits.rs:12:17 @@ -16,17 +16,17 @@ note: required by a bound in `call` LL | fn call(f: impl Fn()) { | ^^^^ required by this bound in `call` -error[E0277]: expected an `FnMut()` closure, found `#[target_features] fn() {foo}` +error[E0277]: expected an `FnMut()` closure, found `#[target_feature(..)] fn() {foo}` --> $DIR/fn-traits.rs:30:14 | LL | call_mut(foo); - | -------- ^^^ expected an `FnMut()` closure, found `#[target_features] fn() {foo}` + | -------- ^^^ expected an `FnMut()` closure, found `#[target_feature(..)] fn() {foo}` | | | required by a bound introduced by this call | - = help: the trait `FnMut()` is not implemented for fn item `#[target_features] fn() {foo}` - = note: wrap the `#[target_features] fn() {foo}` in a closure with no arguments: `|| { /* code */ }` - = note: `#[target_feature]` functions do not implement the `Fn` traits + = help: the trait `FnMut()` is not implemented for fn item `#[target_feature(..)] fn() {foo}` + = note: wrap the `#[target_feature(..)] fn() {foo}` in a closure with no arguments: `|| { /* code */ }` + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call_mut` --> $DIR/fn-traits.rs:16:25 @@ -34,17 +34,17 @@ note: required by a bound in `call_mut` LL | fn call_mut(mut f: impl FnMut()) { | ^^^^^^^ required by this bound in `call_mut` -error[E0277]: expected an `FnOnce()` closure, found `#[target_features] fn() {foo}` +error[E0277]: expected an `FnOnce()` closure, found `#[target_feature(..)] fn() {foo}` --> $DIR/fn-traits.rs:31:15 | LL | call_once(foo); - | --------- ^^^ expected an `FnOnce()` closure, found `#[target_features] fn() {foo}` + | --------- ^^^ expected an `FnOnce()` closure, found `#[target_feature(..)] fn() {foo}` | | | required by a bound introduced by this call | - = help: the trait `FnOnce()` is not implemented for fn item `#[target_features] fn() {foo}` - = note: wrap the `#[target_features] fn() {foo}` in a closure with no arguments: `|| { /* code */ }` - = note: `#[target_feature]` functions do not implement the `Fn` traits + = help: the trait `FnOnce()` is not implemented for fn item `#[target_feature(..)] fn() {foo}` + = note: wrap the `#[target_feature(..)] fn() {foo}` in a closure with no arguments: `|| { /* code */ }` + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call_once` --> $DIR/fn-traits.rs:20:22 @@ -52,16 +52,16 @@ note: required by a bound in `call_once` LL | fn call_once(f: impl FnOnce()) { | ^^^^^^^^ required by this bound in `call_once` -error[E0277]: expected an `FnOnce(i32)` closure, found `#[target_features] fn(i32) {bar}` +error[E0277]: expected an `FnOnce(i32)` closure, found `#[target_feature(..)] fn(i32) {bar}` --> $DIR/fn-traits.rs:32:19 | LL | call_once_i32(bar); - | ------------- ^^^ expected an `FnOnce(i32)` closure, found `#[target_features] fn(i32) {bar}` + | ------------- ^^^ expected an `FnOnce(i32)` closure, found `#[target_feature(..)] fn(i32) {bar}` | | | required by a bound introduced by this call | - = help: the trait `FnOnce(i32)` is not implemented for fn item `#[target_features] fn(i32) {bar}` - = note: `#[target_feature]` functions do not implement the `Fn` traits + = help: the trait `FnOnce(i32)` is not implemented for fn item `#[target_feature(..)] fn(i32) {bar}` + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call_once_i32` --> $DIR/fn-traits.rs:24:26 @@ -80,7 +80,7 @@ LL | call(foo_unsafe); = help: the trait `Fn()` is not implemented for fn item `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ }` = note: unsafe function cannot be called generically without an unsafe block - = note: `#[target_feature]` functions do not implement the `Fn` traits + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call` --> $DIR/fn-traits.rs:12:17 @@ -99,7 +99,7 @@ LL | call_mut(foo_unsafe); = help: the trait `FnMut()` is not implemented for fn item `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ }` = note: unsafe function cannot be called generically without an unsafe block - = note: `#[target_feature]` functions do not implement the `Fn` traits + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call_mut` --> $DIR/fn-traits.rs:16:25 @@ -118,7 +118,7 @@ LL | call_once(foo_unsafe); = help: the trait `FnOnce()` is not implemented for fn item `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ }` = note: unsafe function cannot be called generically without an unsafe block - = note: `#[target_feature]` functions do not implement the `Fn` traits + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call_once` --> $DIR/fn-traits.rs:20:22 diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/trait-impl.stderr b/tests/ui/rfcs/rfc-2396-target_feature-11/trait-impl.stderr index f2ef2b2f3b86b..39b0e5ea18f7d 100644 --- a/tests/ui/rfcs/rfc-2396-target_feature-11/trait-impl.stderr +++ b/tests/ui/rfcs/rfc-2396-target_feature-11/trait-impl.stderr @@ -19,7 +19,7 @@ note: type in trait LL | fn foo(&self); | ^^^^^^^^^^^^^^ = note: expected signature `fn(&Bar)` - found signature `#[target_features] fn(&Bar)` + found signature `#[target_feature(..)] fn(&Bar)` error: `#[target_feature(..)]` cannot be applied to safe trait method --> $DIR/trait-impl.rs:21:5 diff --git a/tests/ui/target-feature/invalid-attribute.stderr b/tests/ui/target-feature/invalid-attribute.stderr index abe3ab431635f..6d45f32c7609c 100644 --- a/tests/ui/target-feature/invalid-attribute.stderr +++ b/tests/ui/target-feature/invalid-attribute.stderr @@ -206,7 +206,7 @@ note: type in trait LL | fn foo(); | ^^^^^^^^^ = note: expected signature `fn()` - found signature `#[target_features] fn()` + found signature `#[target_feature(..)] fn()` error: the feature named `+sse2` is not valid for this target --> $DIR/invalid-attribute.rs:117:18 From 1bd4fa0b6fc4c4f791e26b31d731466890bcab06 Mon Sep 17 00:00:00 2001 From: Yukang Date: Fri, 24 Jul 2026 12:14:01 +0800 Subject: [PATCH 16/63] Add tuple never coercion collection regression test --- ...ever-to-any-coercion-in-collection-issue-100727.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/ui/tuple/never-to-any-coercion-in-collection-issue-100727.rs diff --git a/tests/ui/tuple/never-to-any-coercion-in-collection-issue-100727.rs b/tests/ui/tuple/never-to-any-coercion-in-collection-issue-100727.rs new file mode 100644 index 0000000000000..f44c486e0f50b --- /dev/null +++ b/tests/ui/tuple/never-to-any-coercion-in-collection-issue-100727.rs @@ -0,0 +1,11 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/100727. +//! A tuple element of type `!` should be coerced using constraints from `collect`. + +//@ check-pass +//@ edition: 2021 + +#![allow(unreachable_code)] + +fn main() { + let _: Vec<(i32,)> = [(todo!(),)].into_iter().collect(); +} From 158fdbf013233744ec21c9de1169e5e61db8a971 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 28 Apr 2026 12:08:43 +0200 Subject: [PATCH 17/63] when bailing on ambiguity, don't force other results to ambig --- .../src/solve/search_graph.rs | 9 +-- compiler/rustc_type_ir/src/interner.rs | 2 +- .../rustc_type_ir/src/search_graph/mod.rs | 55 ++++++++----------- .../global-where-bound-normalization.rs | 45 +++++++++++++++ 4 files changed, 71 insertions(+), 40 deletions(-) create mode 100644 tests/ui/traits/next-solver/global-where-bound-normalization.rs diff --git a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs index 4918258350bf3..fb44782ffb039 100644 --- a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs +++ b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs @@ -107,6 +107,7 @@ where response_no_constraints(cx, input, Certainty::overflow(true)) } + const FIXPOINT_OVERFLOW_AMBIGUITY_KIND: Certainty = Certainty::overflow(false); fn fixpoint_overflow_result( cx: I, input: CanonicalInput, @@ -126,14 +127,6 @@ where }) } - fn propagate_ambiguity( - cx: I, - for_input: CanonicalInput, - certainty: Certainty, - ) -> (QueryResult, AccessedOpaques) { - response_no_constraints(cx, for_input, certainty) - } - fn compute_goal( search_graph: &mut SearchGraph, cx: I, diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index bfa1c982bd0b7..fd02f017b2950 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -680,7 +680,7 @@ impl CollectAndApply for Result { impl search_graph::Cx for I { type Input = CanonicalInput; type Result = (QueryResult, AccessedOpaques); - type AmbiguityInfo = Certainty; + type AmbiguityKind = Certainty; type DepNodeIndex = I::DepNodeIndex; type Tracked = I::Tracked; diff --git a/compiler/rustc_type_ir/src/search_graph/mod.rs b/compiler/rustc_type_ir/src/search_graph/mod.rs index 711e7ae1c8945..b1635e7e4097c 100644 --- a/compiler/rustc_type_ir/src/search_graph/mod.rs +++ b/compiler/rustc_type_ir/src/search_graph/mod.rs @@ -40,7 +40,7 @@ pub use global_cache::GlobalCache; pub trait Cx: Copy { type Input: Debug + Eq + Hash + Copy; type Result: Debug + Eq + Hash + Copy; - type AmbiguityInfo: Debug + Eq + Hash + Copy; + type AmbiguityKind: Debug + Eq + Hash + Copy; type DepNodeIndex; type Tracked: Debug; @@ -92,6 +92,8 @@ pub trait Delegate: Sized { cx: Self::Cx, input: ::Input, ) -> ::Result; + + const FIXPOINT_OVERFLOW_AMBIGUITY_KIND: ::AmbiguityKind; fn fixpoint_overflow_result( cx: Self::Cx, input: ::Input, @@ -99,12 +101,7 @@ pub trait Delegate: Sized { fn is_ambiguous_result( result: ::Result, - ) -> Option<::AmbiguityInfo>; - fn propagate_ambiguity( - cx: Self::Cx, - for_input: ::Input, - ambiguity_info: ::AmbiguityInfo, - ) -> ::Result; + ) -> Option<::AmbiguityKind>; fn compute_goal( search_graph: &mut SearchGraph, @@ -955,8 +952,7 @@ impl, X: Cx> SearchGraph { #[derive_where(Debug; X: Cx)] enum RebaseReason { NoCycleUsages, - Ambiguity(X::AmbiguityInfo), - Overflow, + Ambiguity(X::AmbiguityKind), /// We've actually reached a fixpoint. /// /// This either happens in the first evaluation step for the cycle head. @@ -987,10 +983,9 @@ impl, X: Cx> SearchGraph { /// cache entries to also be ambiguous. This causes some undesirable ambiguity for nested /// goals whose result doesn't actually depend on this cycle head, but that's acceptable /// to me. - #[instrument(level = "trace", skip(self, cx))] + #[instrument(level = "trace", skip(self))] fn rebase_provisional_cache_entries( &mut self, - cx: X, stack_entry: &StackEntry, rebase_reason: RebaseReason, ) { @@ -1065,18 +1060,22 @@ impl, X: Cx> SearchGraph { } // The provisional cache entry does depend on the provisional result - // of the popped cycle head. We need to mutate the result of our - // provisional cache entry in case we did not reach a fixpoint. + // of the popped cycle head. In case we didn't actually reach a fixpoint, + // we must not keep potentially incorrect provisional cache entries around. match rebase_reason { // If the cycle head does not actually depend on itself, then // the provisional result used by the provisional cache entry // is not actually equal to the final provisional result. We // need to discard the provisional cache entry in this case. RebaseReason::NoCycleUsages => return false, - RebaseReason::Ambiguity(info) => { - *result = D::propagate_ambiguity(cx, input, info); + // If we avoid rerunning a goal due to ambiguity, we only keep provisional + // results which depend on that cycle head if these are already ambiguous + // themselves. + RebaseReason::Ambiguity(kind) => { + if !D::is_ambiguous_result(*result).is_some_and(|k| k == kind) { + return false; + } } - RebaseReason::Overflow => *result = D::fixpoint_overflow_result(cx, input), RebaseReason::ReachedFixpoint(None) => {} RebaseReason::ReachedFixpoint(Some(path_kind)) => { if !popped_head.usages.is_single(path_kind) { @@ -1380,17 +1379,12 @@ impl, X: Cx> SearchGraph { // final result is equal to the initial response for that case. if let Ok(fixpoint) = self.reached_fixpoint(&stack_entry, usages, result) { self.rebase_provisional_cache_entries( - cx, &stack_entry, RebaseReason::ReachedFixpoint(fixpoint), ); return EvaluationResult::finalize(stack_entry, encountered_overflow, result); } else if usages.is_empty() { - self.rebase_provisional_cache_entries( - cx, - &stack_entry, - RebaseReason::NoCycleUsages, - ); + self.rebase_provisional_cache_entries(&stack_entry, RebaseReason::NoCycleUsages); return EvaluationResult::finalize(stack_entry, encountered_overflow, result); } @@ -1399,19 +1393,15 @@ impl, X: Cx> SearchGraph { // response in the next iteration in this case. These changes would // likely either be caused by incompleteness or can change the maybe // cause from ambiguity to overflow. Returning ambiguity always - // preserves soundness and completeness even if the goal is be known - // to succeed or fail. + // preserves soundness and completeness even if the goal could + // otherwise succeed or fail. // // This prevents exponential blowup affecting multiple major crates. // As we only get to this branch if we haven't yet reached a fixpoint, // we also taint all provisional cache entries which depend on the // current goal. - if let Some(info) = D::is_ambiguous_result(result) { - self.rebase_provisional_cache_entries( - cx, - &stack_entry, - RebaseReason::Ambiguity(info), - ); + if let Some(kind) = D::is_ambiguous_result(result) { + self.rebase_provisional_cache_entries(&stack_entry, RebaseReason::Ambiguity(kind)); return EvaluationResult::finalize(stack_entry, encountered_overflow, result); }; @@ -1421,7 +1411,10 @@ impl, X: Cx> SearchGraph { if i >= D::FIXPOINT_STEP_LIMIT { debug!("canonical cycle overflow"); let result = D::fixpoint_overflow_result(cx, input); - self.rebase_provisional_cache_entries(cx, &stack_entry, RebaseReason::Overflow); + self.rebase_provisional_cache_entries( + &stack_entry, + RebaseReason::Ambiguity(D::FIXPOINT_OVERFLOW_AMBIGUITY_KIND), + ); return EvaluationResult::finalize(stack_entry, encountered_overflow, result); } diff --git a/tests/ui/traits/next-solver/global-where-bound-normalization.rs b/tests/ui/traits/next-solver/global-where-bound-normalization.rs new file mode 100644 index 0000000000000..e57fbf378a0d2 --- /dev/null +++ b/tests/ui/traits/next-solver/global-where-bound-normalization.rs @@ -0,0 +1,45 @@ +//@ check-pass +//@ compile-flags: -Znext-solver + +// Regression test for https://github.com/rust-lang/trait-system-refactor-initiative/issues/257. + +#![feature(rustc_attrs)] +#![expect(internal_features)] +#![rustc_no_implicit_bounds] + +pub trait Bound {} +impl Bound for u8 {} + +pub trait Proj { + type Assoc; +} +impl Proj for U { + type Assoc = U; +} +impl Proj for MyField { + type Assoc = u8; +} + +// While wf-checking the global bounds of `fn foo`, elaborating this outlives predicate triggered a +// cycle in the search graph along a particular probe path, which was not an actual solution. +// That cycle then resulted in a forced false-positive ambiguity due to a performance hack in the +// search graph and then ended up floundering the root goal evaluation. +pub trait Field: Proj {} + +struct MyField; +impl Field for MyField {} + +trait IdReqField { + type This; +} +impl IdReqField for F { + type This = F; +} + +fn foo() +where + ::This: Field, +{ +} + +fn main() {} From f176d8bd2d601214c798c322be447a73de32ca15 Mon Sep 17 00:00:00 2001 From: Vastargazing Date: Fri, 24 Jul 2026 11:05:35 +0300 Subject: [PATCH 18/63] std::sync::poison: disable auto_cfg on PoisonError::new PoisonError::new is defined twice, once under #[cfg(panic = "unwind")] and once under its negation, so exactly one definition exists in any given build. rustdoc's auto_cfg only sees whichever cfg survived expansion and tags the method as available under that cfg only, even though it is callable in every configuration - it just panics under panic=abort instead of returning. Disable auto_cfg on both variants so the portability note stops implying the method doesn't exist elsewhere. The doc comment already explains the panic=abort behavior in prose, so the cfg doesn't need to leak into the generated badge. Add a minimal regression test. --- library/std/src/sync/poison.rs | 2 ++ .../doc-cfg/mutually-exclusive-cfg-item.rs | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/rustdoc-html/doc-cfg/mutually-exclusive-cfg-item.rs diff --git a/library/std/src/sync/poison.rs b/library/std/src/sync/poison.rs index ee7e5f8586700..3c32ec34dee5b 100644 --- a/library/std/src/sync/poison.rs +++ b/library/std/src/sync/poison.rs @@ -263,6 +263,7 @@ impl PoisonError { /// or [`RwLock::read`](crate::sync::RwLock::read). /// /// This method may panic if std was built with `panic="abort"`. + #[doc(auto_cfg = false)] #[cfg(panic = "unwind")] #[stable(feature = "sync_poison", since = "1.2.0")] pub fn new(data: T) -> PoisonError { @@ -275,6 +276,7 @@ impl PoisonError { /// or [`RwLock::read`](crate::sync::RwLock::read). /// /// This method may panic if std was built with `panic="abort"`. + #[doc(auto_cfg = false)] #[cfg(not(panic = "unwind"))] #[stable(feature = "sync_poison", since = "1.2.0")] #[track_caller] diff --git a/tests/rustdoc-html/doc-cfg/mutually-exclusive-cfg-item.rs b/tests/rustdoc-html/doc-cfg/mutually-exclusive-cfg-item.rs new file mode 100644 index 0000000000000..d168bcdf0b41a --- /dev/null +++ b/tests/rustdoc-html/doc-cfg/mutually-exclusive-cfg-item.rs @@ -0,0 +1,24 @@ +// An item split into mutually-exclusive `#[cfg(..)]` variants that together cover +// every configuration must not get a portability note when `auto_cfg` is disabled. +// Regression test for . + +#![feature(doc_cfg)] +#![crate_name = "foo"] + +pub struct S; + +impl S { + //@ has 'foo/struct.S.html' + //@ count - '//*[@id="method.new"]/..//*[@class="stab portability"]' 0 + #[doc(auto_cfg = false)] + #[cfg(panic = "unwind")] + pub fn new() -> S { + S + } + + #[doc(auto_cfg = false)] + #[cfg(not(panic = "unwind"))] + pub fn new() -> S { + S + } +} From 8aafa5e3ed9ae8b6094e7b458566a28824646a8b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 16 Jul 2026 23:29:41 +0200 Subject: [PATCH 19/63] [rustdoc] Correctly handle output options with `--show-coverage` --- .../{passes => }/calculate_doc_coverage.rs | 80 +++++++++++-------- src/librustdoc/config.rs | 9 ++- src/librustdoc/core.rs | 7 ++ src/librustdoc/lib.rs | 5 +- src/librustdoc/passes/mod.rs | 9 +-- 5 files changed, 69 insertions(+), 41 deletions(-) rename src/librustdoc/{passes => }/calculate_doc_coverage.rs (81%) diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/calculate_doc_coverage.rs similarity index 81% rename from src/librustdoc/passes/calculate_doc_coverage.rs rename to src/librustdoc/calculate_doc_coverage.rs index adf98afc46cc0..65bab8609f008 100644 --- a/src/librustdoc/passes/calculate_doc_coverage.rs +++ b/src/librustdoc/calculate_doc_coverage.rs @@ -1,6 +1,8 @@ //! Calculates information used for the --show-coverage flag. use std::collections::BTreeMap; +use std::fs::{File, create_dir_all}; +use std::io::{self, BufWriter, Write, stdout}; use std::ops; use rustc_hir as hir; @@ -10,27 +12,37 @@ use rustc_span::{FileName, RemapPathScopeComponents}; use serde::Serialize; use tracing::debug; -use crate::clean; -use crate::config::OutputFormat; +use crate::config::{OutputFormat, RenderOptions}; use crate::core::DocContext; +use crate::docfs::PathError; +use crate::error::Error; use crate::html::markdown::{ErrorCodes, find_testable_code}; -use crate::passes::Pass; -use crate::passes::check_doc_test_visibility::{Tests, should_have_doc_example}; +use crate::passes::{Tests, should_have_doc_example}; use crate::visit::DocVisitor; - -pub(crate) const CALCULATE_DOC_COVERAGE: Pass = Pass { - name: "calculate-doc-coverage", - run: Some(calculate_doc_coverage), - description: "counts the number of items with and without documentation", -}; - -fn calculate_doc_coverage(krate: clean::Crate, ctx: &mut DocContext<'_>) -> clean::Crate { +use crate::{clean, try_err}; + +pub(crate) fn run( + krate: &clean::Crate, + ctx: &mut DocContext<'_>, + options: &RenderOptions, +) -> Result<(), Error> { + let is_json = ctx.output_format == OutputFormat::CoverageJson; + let tcx = ctx.tcx; let mut calc = CoverageCalculator { items: Default::default(), ctx }; calc.visit_crate(&krate); - calc.print_results(); - - krate + if options.output_to_stdout { + calc.print_results(BufWriter::new(stdout().lock())) + .map_err(|error| Error::new(error, "")) + } else { + let out_dir = &options.output; + try_err!(create_dir_all(out_dir), out_dir); + let name = krate.name(tcx); + let mut out_file = out_dir.join(name.as_str()); + out_file.set_extension(if is_json { "json" } else { "txt" }); + let buf = try_err!(File::create_buffered(&out_file), out_file); + calc.print_results(buf).map_err(|error| Error::new(error, out_file)) + } } #[derive(Default, Copy, Clone, Serialize, Debug)] @@ -130,62 +142,66 @@ impl CoverageCalculator<'_, '_> { .expect("failed to convert JSON data to string") } - fn print_results(&self) { + fn print_results(&self, mut buf: impl Write) -> io::Result<()> { let output_format = self.ctx.output_format; if output_format == OutputFormat::CoverageJson { - println!("{}", self.to_json()); - return; + return writeln!(buf, "{}", self.to_json()); } let mut total = ItemCount::default(); - fn print_table_line() { - println!("+-{0:->35}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+", ""); + fn print_table_line(buf: &mut impl Write) -> io::Result<()> { + writeln!(buf, "+-{0:->35}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+-{0:->10}-+", "") } fn print_table_record( + buf: &mut impl Write, name: &str, count: ItemCount, percentage: f64, examples_percentage: f64, - ) { - println!( + ) -> io::Result<()> { + writeln!( + buf, "| {name:<35} | {with_docs:>10} | {percentage:>9.1}% | {with_examples:>10} | \ - {examples_percentage:>9.1}% |", + {examples_percentage:>9.1}% |", with_docs = count.with_docs, with_examples = count.with_examples, - ); + ) } - print_table_line(); - println!( + print_table_line(&mut buf)?; + writeln!( + buf, "| {:<35} | {:>10} | {:>10} | {:>10} | {:>10} |", "File", "Documented", "Percentage", "Examples", "Percentage", - ); - print_table_line(); + )?; + print_table_line(&mut buf)?; for (file, &count) in &self.items { if let Some(percentage) = count.percentage() { print_table_record( + &mut buf, &limit_filename_len( file.display(RemapPathScopeComponents::COVERAGE).to_string(), ), count, percentage, count.examples_percentage().unwrap_or(0.), - ); + )?; total += count; } } - print_table_line(); + print_table_line(&mut buf)?; print_table_record( + &mut buf, "Total", total, total.percentage().unwrap_or(0.0), total.examples_percentage().unwrap_or(0.0), - ); - print_table_line(); + )?; + print_table_line(&mut buf) } } diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 840f9d025c3cf..0a3cbf537e5f1 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -726,7 +726,14 @@ impl Options { output_to_stdout = out_dir == "-"; PathBuf::from(out_dir) } - (None, None) => PathBuf::from("doc"), + (None, None) => { + if show_coverage { + // If no `-o` option is given and we're in the `--show-coverage` mode, by + // default we print on the stdout. + output_to_stdout = true; + } + PathBuf::from("doc") + } }; let cfgs = matches.opt_strs("cfg"); diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index c1ae5f977cb89..af1024580c544 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -452,6 +452,13 @@ pub(crate) fn run_global_ctxt( } } + if show_coverage + && let Err(error) = crate::calculate_doc_coverage::run(&krate, &mut ctxt, &render_options) + { + eprintln!("{error}"); + std::process::exit(1); + } + tcx.sess.time("check_lint_expectations", || tcx.check_expectations(Some(sym::rustdoc))); krate = diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index be830cad6c735..0fadd78fd30b1 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -105,6 +105,7 @@ macro_rules! map { }} } +mod calculate_doc_coverage; mod clean; mod config; mod core; @@ -955,14 +956,14 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { return scrape_examples::run(krate, render_opts, cache, tcx, options, bin_crate); } - cache.crate_version = crate_version; - if show_coverage { // if we ran coverage, bail early, we don't need to also generate docs at this point // (also we didn't load in any of the useful passes) return; } + cache.crate_version = crate_version; + rustc_interface::passes::emit_delayed_lints(tcx); if render_opts.dep_info().is_some() { diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index 18e1afaf8a242..725c2be4e2121 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -30,14 +30,13 @@ pub(crate) mod collect_intra_doc_links; pub(crate) use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS; mod check_doc_test_visibility; -pub(crate) use self::check_doc_test_visibility::CHECK_DOC_TEST_VISIBILITY; +pub(crate) use self::check_doc_test_visibility::{ + CHECK_DOC_TEST_VISIBILITY, Tests, should_have_doc_example, +}; mod collect_trait_impls; pub(crate) use self::collect_trait_impls::COLLECT_TRAIT_IMPLS; -mod calculate_doc_coverage; -pub(crate) use self::calculate_doc_coverage::CALCULATE_DOC_COVERAGE; - mod lint; pub(crate) use self::lint::RUN_LINTS; @@ -81,7 +80,6 @@ pub(crate) const PASSES: &[Pass] = &[ PROPAGATE_STABILITY, COLLECT_INTRA_DOC_LINKS, COLLECT_TRAIT_IMPLS, - CALCULATE_DOC_COVERAGE, RUN_LINTS, ]; @@ -103,7 +101,6 @@ pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[ pub(crate) const COVERAGE_PASSES: &[ConditionalPass] = &[ ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden), ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate), - ConditionalPass::always(CALCULATE_DOC_COVERAGE), ]; impl ConditionalPass { From f545db6b9f261b1dc3c58c6d42865fe729960933 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 16 Jul 2026 23:30:02 +0200 Subject: [PATCH 20/63] Add regression test for `--show-coverage` output --- tests/run-make/rustdoc-show-coverage/foo.rs | 5 ++ tests/run-make/rustdoc-show-coverage/rmake.rs | 53 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 tests/run-make/rustdoc-show-coverage/foo.rs create mode 100644 tests/run-make/rustdoc-show-coverage/rmake.rs diff --git a/tests/run-make/rustdoc-show-coverage/foo.rs b/tests/run-make/rustdoc-show-coverage/foo.rs new file mode 100644 index 0000000000000..4892b4ce1ae69 --- /dev/null +++ b/tests/run-make/rustdoc-show-coverage/foo.rs @@ -0,0 +1,5 @@ +pub struct Bar; + +impl Bar { + pub fn foo() {} +} diff --git a/tests/run-make/rustdoc-show-coverage/rmake.rs b/tests/run-make/rustdoc-show-coverage/rmake.rs new file mode 100644 index 0000000000000..eb54bf7e544ca --- /dev/null +++ b/tests/run-make/rustdoc-show-coverage/rmake.rs @@ -0,0 +1,53 @@ +// This test ensures that `-o` option works as expected with `--show-coverage`. +// Regression test for . + +use run_make_support::{path, rustdoc}; +use run_make_support::rfs::{read_to_string, remove_file}; + +fn run_rustdoc(extra_args: &[&str]) -> String { + rustdoc() + .input("foo.rs") + .arg("-Zunstable-options") + .arg("--show-coverage") + .args(extra_args) + .run() + .stdout_utf8() +} + +fn check_print_stdout(extra_args: &[&str], stdout_check: &str) { + let out = run_rustdoc(extra_args); + + // By default, it shouldn't have created a `doc` folder. + assert!(!path("doc").exists(), "`doc` folder created with {extra_args:?}"); + // It should have display its output on stdout. + assert!(out.starts_with(stdout_check), "{out:?} doesn't start with {stdout_check:?}"); +} + +fn check_generate_file(ext: &str, extra_args: &[&str], file_check: &str) { + let mut args = extra_args.to_vec(); + args.push("-o"); + args.push("doc"); + let out = run_rustdoc(&args); + + // By default, it shouldn't have created a `doc` folder. + assert!(path("doc").exists(), "`doc` folder not created with {args:?}"); + let file = format!("doc/foo.{ext}"); + assert!(path(&file).exists()); + + assert!(out.is_empty(), "Shouldn't display anything to stdout and yet we got {out:?}"); + + let content = read_to_string(&file); + assert!(content.starts_with(file_check), "{content:?} doesn't start with {file_check:?}"); + remove_file(file); +} + +fn main() { + check_print_stdout(&[], "+-"); + check_print_stdout(&["-o", "-"], "+-"); + check_print_stdout(&["--output-format=json"], "{"); + check_print_stdout(&["--output-format=json", "-o", "-"], "{"); + + // Now we check that it works with "-o something". + check_generate_file("txt", &[], "+-"); + check_generate_file("json", &["--output-format=json"], "{"); +} From 65b6e98a104e0427bd66e50bdf3bca655320496f Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 16 Jul 2026 23:34:15 +0200 Subject: [PATCH 21/63] Add small message to say where the output was generated --- src/librustdoc/calculate_doc_coverage.rs | 4 +++- tests/run-make/rustdoc-show-coverage/rmake.rs | 5 +++-- tests/rustdoc-ui/coverage/allow_missing_docs.rs | 2 +- tests/rustdoc-ui/coverage/basic.rs | 2 +- tests/rustdoc-ui/coverage/doc-examples-json.rs | 2 +- tests/rustdoc-ui/coverage/doc-examples.rs | 2 +- tests/rustdoc-ui/coverage/empty.rs | 2 +- tests/rustdoc-ui/coverage/enum-tuple-documented.rs | 2 +- tests/rustdoc-ui/coverage/enum-tuple.rs | 2 +- tests/rustdoc-ui/coverage/enums.rs | 2 +- tests/rustdoc-ui/coverage/exotic.rs | 2 +- tests/rustdoc-ui/coverage/json.rs | 2 +- tests/rustdoc-ui/coverage/private.rs | 2 +- tests/rustdoc-ui/coverage/statics-consts.rs | 2 +- tests/rustdoc-ui/coverage/traits.rs | 2 +- tests/rustdoc-ui/issues/issue-91713.stdout | 2 -- tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.rs | 2 +- tests/rustdoc-ui/show-coverage-json.rs | 2 +- tests/rustdoc-ui/show-coverage.rs | 2 +- 19 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/librustdoc/calculate_doc_coverage.rs b/src/librustdoc/calculate_doc_coverage.rs index 65bab8609f008..ae05e362f2383 100644 --- a/src/librustdoc/calculate_doc_coverage.rs +++ b/src/librustdoc/calculate_doc_coverage.rs @@ -41,7 +41,9 @@ pub(crate) fn run( let mut out_file = out_dir.join(name.as_str()); out_file.set_extension(if is_json { "json" } else { "txt" }); let buf = try_err!(File::create_buffered(&out_file), out_file); - calc.print_results(buf).map_err(|error| Error::new(error, out_file)) + calc.print_results(buf).map_err(|error| Error::new(error, &out_file))?; + println!("Generated output into {out_file:?}"); + Ok(()) } } diff --git a/tests/run-make/rustdoc-show-coverage/rmake.rs b/tests/run-make/rustdoc-show-coverage/rmake.rs index eb54bf7e544ca..59693b2829483 100644 --- a/tests/run-make/rustdoc-show-coverage/rmake.rs +++ b/tests/run-make/rustdoc-show-coverage/rmake.rs @@ -1,8 +1,8 @@ // This test ensures that `-o` option works as expected with `--show-coverage`. // Regression test for . -use run_make_support::{path, rustdoc}; use run_make_support::rfs::{read_to_string, remove_file}; +use run_make_support::{path, rustdoc}; fn run_rustdoc(extra_args: &[&str]) -> String { rustdoc() @@ -34,7 +34,8 @@ fn check_generate_file(ext: &str, extra_args: &[&str], file_check: &str) { let file = format!("doc/foo.{ext}"); assert!(path(&file).exists()); - assert!(out.is_empty(), "Shouldn't display anything to stdout and yet we got {out:?}"); + let expected = format!("Generated output into {file:?}\n"); + assert_eq!(out, expected, "Expected {expected:?}, got {out:?}"); let content = read_to_string(&file); assert!(content.starts_with(file_check), "{content:?} doesn't start with {file_check:?}"); diff --git a/tests/rustdoc-ui/coverage/allow_missing_docs.rs b/tests/rustdoc-ui/coverage/allow_missing_docs.rs index 43f0d731fdea0..feca5a1bfe3e9 100644 --- a/tests/rustdoc-ui/coverage/allow_missing_docs.rs +++ b/tests/rustdoc-ui/coverage/allow_missing_docs.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage +//@ compile-flags:-Z unstable-options --show-coverage -o - //@ check-pass //! Make sure to have some docs on your crate root diff --git a/tests/rustdoc-ui/coverage/basic.rs b/tests/rustdoc-ui/coverage/basic.rs index febcc80fbbb50..fc44f6826cb9c 100644 --- a/tests/rustdoc-ui/coverage/basic.rs +++ b/tests/rustdoc-ui/coverage/basic.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage +//@ compile-flags:-Z unstable-options --show-coverage -o - //@ check-pass #![feature(extern_types)] diff --git a/tests/rustdoc-ui/coverage/doc-examples-json.rs b/tests/rustdoc-ui/coverage/doc-examples-json.rs index 4aa4bf23771d9..bfec2600cb875 100644 --- a/tests/rustdoc-ui/coverage/doc-examples-json.rs +++ b/tests/rustdoc-ui/coverage/doc-examples-json.rs @@ -1,5 +1,5 @@ //@ check-pass -//@ compile-flags:-Z unstable-options --output-format json --show-coverage +//@ compile-flags:-Z unstable-options --output-format json --show-coverage -o - // This check ensures that only one doc example is counted since they're "optional" on // certain items. diff --git a/tests/rustdoc-ui/coverage/doc-examples.rs b/tests/rustdoc-ui/coverage/doc-examples.rs index 283d9c424aa19..dd8969f934ee2 100644 --- a/tests/rustdoc-ui/coverage/doc-examples.rs +++ b/tests/rustdoc-ui/coverage/doc-examples.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage +//@ compile-flags:-Z unstable-options --show-coverage -o - //@ check-pass //! This test ensure that only rust code examples are counted. diff --git a/tests/rustdoc-ui/coverage/empty.rs b/tests/rustdoc-ui/coverage/empty.rs index bcd3e48988b15..fd9f1e617fbec 100644 --- a/tests/rustdoc-ui/coverage/empty.rs +++ b/tests/rustdoc-ui/coverage/empty.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage +//@ compile-flags:-Z unstable-options --show-coverage -o - //@ check-pass // an empty crate still has one item to document: the crate root diff --git a/tests/rustdoc-ui/coverage/enum-tuple-documented.rs b/tests/rustdoc-ui/coverage/enum-tuple-documented.rs index 4cbeb7a164da1..9e88e896f8da7 100644 --- a/tests/rustdoc-ui/coverage/enum-tuple-documented.rs +++ b/tests/rustdoc-ui/coverage/enum-tuple-documented.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage +//@ compile-flags:-Z unstable-options --show-coverage -o - //@ check-pass // The point of this test is to ensure that the number of "documented" items diff --git a/tests/rustdoc-ui/coverage/enum-tuple.rs b/tests/rustdoc-ui/coverage/enum-tuple.rs index 5cbc52a7d033f..25d3c9f57bafe 100644 --- a/tests/rustdoc-ui/coverage/enum-tuple.rs +++ b/tests/rustdoc-ui/coverage/enum-tuple.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage +//@ compile-flags:-Z unstable-options --show-coverage -o - //@ check-pass //! (remember the crate root is still a module) diff --git a/tests/rustdoc-ui/coverage/enums.rs b/tests/rustdoc-ui/coverage/enums.rs index 29e4198457610..bdb213c2a53b7 100644 --- a/tests/rustdoc-ui/coverage/enums.rs +++ b/tests/rustdoc-ui/coverage/enums.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage +//@ compile-flags:-Z unstable-options --show-coverage -o - //@ check-pass //! (remember the crate root is still a module) diff --git a/tests/rustdoc-ui/coverage/exotic.rs b/tests/rustdoc-ui/coverage/exotic.rs index 9fc1498cb2a3a..2beb890b21926 100644 --- a/tests/rustdoc-ui/coverage/exotic.rs +++ b/tests/rustdoc-ui/coverage/exotic.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage +//@ compile-flags:-Z unstable-options --show-coverage -o - //@ check-pass #![feature(rustdoc_internals)] diff --git a/tests/rustdoc-ui/coverage/json.rs b/tests/rustdoc-ui/coverage/json.rs index bfa8dc7008305..dcab7df252249 100644 --- a/tests/rustdoc-ui/coverage/json.rs +++ b/tests/rustdoc-ui/coverage/json.rs @@ -1,5 +1,5 @@ //@ check-pass -//@ compile-flags:-Z unstable-options --output-format json --show-coverage +//@ compile-flags:-Z unstable-options --output-format json --show-coverage -o - pub mod foo { /// Hello! diff --git a/tests/rustdoc-ui/coverage/private.rs b/tests/rustdoc-ui/coverage/private.rs index 91490eff7a8d5..3b78c5d761dbc 100644 --- a/tests/rustdoc-ui/coverage/private.rs +++ b/tests/rustdoc-ui/coverage/private.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage --document-private-items +//@ compile-flags:-Z unstable-options --show-coverage --document-private-items -o - //@ check-pass #![allow(unused)] diff --git a/tests/rustdoc-ui/coverage/statics-consts.rs b/tests/rustdoc-ui/coverage/statics-consts.rs index 85cc23847396e..5177673f7958a 100644 --- a/tests/rustdoc-ui/coverage/statics-consts.rs +++ b/tests/rustdoc-ui/coverage/statics-consts.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage +//@ compile-flags:-Z unstable-options --show-coverage -o - //@ check-pass //! gotta make sure we can count statics and consts correctly, too diff --git a/tests/rustdoc-ui/coverage/traits.rs b/tests/rustdoc-ui/coverage/traits.rs index 89044369e6a77..37e147004d714 100644 --- a/tests/rustdoc-ui/coverage/traits.rs +++ b/tests/rustdoc-ui/coverage/traits.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage +//@ compile-flags:-Z unstable-options --show-coverage -o - //@ check-pass #![feature(trait_alias)] diff --git a/tests/rustdoc-ui/issues/issue-91713.stdout b/tests/rustdoc-ui/issues/issue-91713.stdout index e7b8a1dccf802..0243f6cd533a0 100644 --- a/tests/rustdoc-ui/issues/issue-91713.stdout +++ b/tests/rustdoc-ui/issues/issue-91713.stdout @@ -8,7 +8,6 @@ strip-aliased-non-local - strips all non-local private aliased items from the ou propagate-stability - propagates stability to child items collect-intra-doc-links - resolves intra-doc links collect-trait-impls - retrieves trait impls for items in the crate -calculate-doc-coverage - counts the number of items with and without documentation run-lints - runs some of rustdoc's lints Default passes for rustdoc: @@ -26,4 +25,3 @@ collect-intra-doc-links Passes run with `--show-coverage`: strip-hidden (when not --document-hidden-items) strip-private (when not --document-private-items) -calculate-doc-coverage diff --git a/tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.rs b/tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.rs index 0609b515e5ca8..97cdedecb0aaf 100644 --- a/tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.rs +++ b/tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.rs @@ -1,2 +1,2 @@ -//@ compile-flags: --show-coverage --output-format=json --emit=dep-info -Zunstable-options +//@ compile-flags: --show-coverage --output-format=json --emit=dep-info -Zunstable-options -o - //@ build-pass diff --git a/tests/rustdoc-ui/show-coverage-json.rs b/tests/rustdoc-ui/show-coverage-json.rs index 3851e34fe3599..e7ddc69d79f8a 100644 --- a/tests/rustdoc-ui/show-coverage-json.rs +++ b/tests/rustdoc-ui/show-coverage-json.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Z unstable-options --show-coverage --output-format=json +//@ compile-flags: -Z unstable-options --show-coverage --output-format=json -o - //@ check-pass mod bar { diff --git a/tests/rustdoc-ui/show-coverage.rs b/tests/rustdoc-ui/show-coverage.rs index 00bb1606a82cb..1f9c3a8f805b7 100644 --- a/tests/rustdoc-ui/show-coverage.rs +++ b/tests/rustdoc-ui/show-coverage.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Z unstable-options --show-coverage +//@ compile-flags: -Z unstable-options --show-coverage -o - //@ check-pass mod bar { From 854bbe76f0897aeae84b441a6c1d6b9c95a52811 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 23 Jul 2026 12:16:04 +0200 Subject: [PATCH 22/63] Fix `tests/run-make/rustdoc-show-coverage/rmake.rs` by requiring `needs-target-std` --- tests/run-make/rustdoc-show-coverage/rmake.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/run-make/rustdoc-show-coverage/rmake.rs b/tests/run-make/rustdoc-show-coverage/rmake.rs index 59693b2829483..cc4956f2c58c2 100644 --- a/tests/run-make/rustdoc-show-coverage/rmake.rs +++ b/tests/run-make/rustdoc-show-coverage/rmake.rs @@ -1,6 +1,8 @@ // This test ensures that `-o` option works as expected with `--show-coverage`. // Regression test for . +//@ needs-target-std + use run_make_support::rfs::{read_to_string, remove_file}; use run_make_support::{path, rustdoc}; From 098056ae19c91fd03701e36476a166cb5d84f809 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 24 Jul 2026 12:09:48 +0200 Subject: [PATCH 23/63] Move `rustdoc-ui` `--show-coverage` tests into the `coverage` folder --- tests/rustdoc-ui/{ => coverage}/doctest-output.rs | 0 tests/rustdoc-ui/{ => coverage}/doctest-output.stderr | 0 .../{ => coverage}/output-format-coveragejson-emit-depinfo.rs | 0 .../output-format-coveragejson-emit-depinfo.stdout | 0 .../output-format-json-emit-html.html_non_static.stderr | 0 ...output-format-json-emit-html.html_non_static_coverage.stderr | 0 .../output-format-json-emit-html.html_static.stderr | 0 .../output-format-json-emit-html.html_static_coverage.stderr | 0 tests/rustdoc-ui/{ => coverage}/output-format-json-emit-html.rs | 0 .../{ => coverage}/show-coverage-json-emit-html-non-static.rs | 0 .../show-coverage-json-emit-html-non-static.stderr | 0 tests/rustdoc-ui/{ => coverage}/show-coverage-json.rs | 0 tests/rustdoc-ui/{ => coverage}/show-coverage-json.stdout | 0 tests/rustdoc-ui/{ => coverage}/show-coverage.rs | 0 tests/rustdoc-ui/{ => coverage}/show-coverage.stdout | 2 +- 15 files changed, 1 insertion(+), 1 deletion(-) rename tests/rustdoc-ui/{ => coverage}/doctest-output.rs (100%) rename tests/rustdoc-ui/{ => coverage}/doctest-output.stderr (100%) rename tests/rustdoc-ui/{ => coverage}/output-format-coveragejson-emit-depinfo.rs (100%) rename tests/rustdoc-ui/{ => coverage}/output-format-coveragejson-emit-depinfo.stdout (100%) rename tests/rustdoc-ui/{ => coverage}/output-format-json-emit-html.html_non_static.stderr (100%) rename tests/rustdoc-ui/{ => coverage}/output-format-json-emit-html.html_non_static_coverage.stderr (100%) rename tests/rustdoc-ui/{ => coverage}/output-format-json-emit-html.html_static.stderr (100%) rename tests/rustdoc-ui/{ => coverage}/output-format-json-emit-html.html_static_coverage.stderr (100%) rename tests/rustdoc-ui/{ => coverage}/output-format-json-emit-html.rs (100%) rename tests/rustdoc-ui/{ => coverage}/show-coverage-json-emit-html-non-static.rs (100%) rename tests/rustdoc-ui/{ => coverage}/show-coverage-json-emit-html-non-static.stderr (100%) rename tests/rustdoc-ui/{ => coverage}/show-coverage-json.rs (100%) rename tests/rustdoc-ui/{ => coverage}/show-coverage-json.stdout (100%) rename tests/rustdoc-ui/{ => coverage}/show-coverage.rs (100%) rename tests/rustdoc-ui/{ => coverage}/show-coverage.stdout (90%) diff --git a/tests/rustdoc-ui/doctest-output.rs b/tests/rustdoc-ui/coverage/doctest-output.rs similarity index 100% rename from tests/rustdoc-ui/doctest-output.rs rename to tests/rustdoc-ui/coverage/doctest-output.rs diff --git a/tests/rustdoc-ui/doctest-output.stderr b/tests/rustdoc-ui/coverage/doctest-output.stderr similarity index 100% rename from tests/rustdoc-ui/doctest-output.stderr rename to tests/rustdoc-ui/coverage/doctest-output.stderr diff --git a/tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.rs b/tests/rustdoc-ui/coverage/output-format-coveragejson-emit-depinfo.rs similarity index 100% rename from tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.rs rename to tests/rustdoc-ui/coverage/output-format-coveragejson-emit-depinfo.rs diff --git a/tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.stdout b/tests/rustdoc-ui/coverage/output-format-coveragejson-emit-depinfo.stdout similarity index 100% rename from tests/rustdoc-ui/output-format-coveragejson-emit-depinfo.stdout rename to tests/rustdoc-ui/coverage/output-format-coveragejson-emit-depinfo.stdout diff --git a/tests/rustdoc-ui/output-format-json-emit-html.html_non_static.stderr b/tests/rustdoc-ui/coverage/output-format-json-emit-html.html_non_static.stderr similarity index 100% rename from tests/rustdoc-ui/output-format-json-emit-html.html_non_static.stderr rename to tests/rustdoc-ui/coverage/output-format-json-emit-html.html_non_static.stderr diff --git a/tests/rustdoc-ui/output-format-json-emit-html.html_non_static_coverage.stderr b/tests/rustdoc-ui/coverage/output-format-json-emit-html.html_non_static_coverage.stderr similarity index 100% rename from tests/rustdoc-ui/output-format-json-emit-html.html_non_static_coverage.stderr rename to tests/rustdoc-ui/coverage/output-format-json-emit-html.html_non_static_coverage.stderr diff --git a/tests/rustdoc-ui/output-format-json-emit-html.html_static.stderr b/tests/rustdoc-ui/coverage/output-format-json-emit-html.html_static.stderr similarity index 100% rename from tests/rustdoc-ui/output-format-json-emit-html.html_static.stderr rename to tests/rustdoc-ui/coverage/output-format-json-emit-html.html_static.stderr diff --git a/tests/rustdoc-ui/output-format-json-emit-html.html_static_coverage.stderr b/tests/rustdoc-ui/coverage/output-format-json-emit-html.html_static_coverage.stderr similarity index 100% rename from tests/rustdoc-ui/output-format-json-emit-html.html_static_coverage.stderr rename to tests/rustdoc-ui/coverage/output-format-json-emit-html.html_static_coverage.stderr diff --git a/tests/rustdoc-ui/output-format-json-emit-html.rs b/tests/rustdoc-ui/coverage/output-format-json-emit-html.rs similarity index 100% rename from tests/rustdoc-ui/output-format-json-emit-html.rs rename to tests/rustdoc-ui/coverage/output-format-json-emit-html.rs diff --git a/tests/rustdoc-ui/show-coverage-json-emit-html-non-static.rs b/tests/rustdoc-ui/coverage/show-coverage-json-emit-html-non-static.rs similarity index 100% rename from tests/rustdoc-ui/show-coverage-json-emit-html-non-static.rs rename to tests/rustdoc-ui/coverage/show-coverage-json-emit-html-non-static.rs diff --git a/tests/rustdoc-ui/show-coverage-json-emit-html-non-static.stderr b/tests/rustdoc-ui/coverage/show-coverage-json-emit-html-non-static.stderr similarity index 100% rename from tests/rustdoc-ui/show-coverage-json-emit-html-non-static.stderr rename to tests/rustdoc-ui/coverage/show-coverage-json-emit-html-non-static.stderr diff --git a/tests/rustdoc-ui/show-coverage-json.rs b/tests/rustdoc-ui/coverage/show-coverage-json.rs similarity index 100% rename from tests/rustdoc-ui/show-coverage-json.rs rename to tests/rustdoc-ui/coverage/show-coverage-json.rs diff --git a/tests/rustdoc-ui/show-coverage-json.stdout b/tests/rustdoc-ui/coverage/show-coverage-json.stdout similarity index 100% rename from tests/rustdoc-ui/show-coverage-json.stdout rename to tests/rustdoc-ui/coverage/show-coverage-json.stdout diff --git a/tests/rustdoc-ui/show-coverage.rs b/tests/rustdoc-ui/coverage/show-coverage.rs similarity index 100% rename from tests/rustdoc-ui/show-coverage.rs rename to tests/rustdoc-ui/coverage/show-coverage.rs diff --git a/tests/rustdoc-ui/show-coverage.stdout b/tests/rustdoc-ui/coverage/show-coverage.stdout similarity index 90% rename from tests/rustdoc-ui/show-coverage.stdout rename to tests/rustdoc-ui/coverage/show-coverage.stdout index b9e0316545e77..42c17f8bbd696 100644 --- a/tests/rustdoc-ui/show-coverage.stdout +++ b/tests/rustdoc-ui/coverage/show-coverage.stdout @@ -1,7 +1,7 @@ +-------------------------------------+------------+------------+------------+------------+ | File | Documented | Percentage | Examples | Percentage | +-------------------------------------+------------+------------+------------+------------+ -| ...ests/rustdoc-ui/show-coverage.rs | 1 | 50.0% | 1 | 100.0% | +| ...doc-ui/coverage/show-coverage.rs | 1 | 50.0% | 1 | 100.0% | +-------------------------------------+------------+------------+------------+------------+ | Total | 1 | 50.0% | 1 | 100.0% | +-------------------------------------+------------+------------+------------+------------+ From f8711a10fd8d25ea534d121745e023a7c886a54e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Jul 2026 22:12:06 +0200 Subject: [PATCH 24/63] reuse regular exported_non_generic_symbols logic in Miri --- compiler/rustc_codegen_ssa/src/back/mod.rs | 2 + .../src/back/symbol_export.rs | 12 ++++ compiler/rustc_passes/src/reachable.rs | 2 + src/tools/miri/src/bin/miri.rs | 66 +++---------------- src/tools/miri/src/helpers.rs | 52 ++++++++------- src/tools/miri/src/shims/foreign_items.rs | 6 +- 6 files changed, 55 insertions(+), 85 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/mod.rs b/compiler/rustc_codegen_ssa/src/back/mod.rs index 0f5050a9d7a9a..68db2d0cbf0bb 100644 --- a/compiler/rustc_codegen_ssa/src/back/mod.rs +++ b/compiler/rustc_codegen_ssa/src/back/mod.rs @@ -15,6 +15,8 @@ mod symbol_edit; pub mod symbol_export; pub mod write; +pub use symbol_export::{exported_non_generic_symbols_helper, reachable_non_generics_helper}; + /// The target triple depends on the deployment target, and is required to /// enable features such as cross-language LTO, and for picking the right /// Mach-O commands. diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index 42e2b646e4793..51413d3a67a68 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -54,6 +54,11 @@ fn reachable_non_generics_provider(tcx: TyCtxt<'_>, _: LocalCrate) -> DefIdMap) -> DefIdMap { let is_compiler_builtins = tcx.is_compiler_builtins(LOCAL_CRATE); let mut reachable_non_generics: DefIdMap<_> = tcx @@ -176,6 +181,13 @@ fn exported_non_generic_symbols_provider_local<'tcx>( return &[]; } + exported_non_generic_symbols_helper(tcx) +} + +/// Exposed separately *without* the "should codegen" check so Miri can access it. +pub fn exported_non_generic_symbols_helper<'tcx>( + tcx: TyCtxt<'tcx>, +) -> &'tcx [(ExportedSymbol<'tcx>, SymbolExportInfo)] { // FIXME: Sorting this is unnecessary since we are sorting later anyway. // Can we skip the later sorting? let sorted = tcx.with_stable_hashing_context(|mut hcx| { diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs index fdae03ec49d9f..b8497aefb7767 100644 --- a/compiler/rustc_passes/src/reachable.rs +++ b/compiler/rustc_passes/src/reachable.rs @@ -444,6 +444,8 @@ fn has_custom_linkage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { // FIXME(nbdd0121): `#[used]` are marked as reachable here so it's picked up by // `linked_symbols` in cg_ssa. They won't be exported in binary or cdylib due to their // `SymbolExportLevel::Rust` export level but may end up being exported in dylibs. + // Also note that Miri is relying on this to be able to find private `link_section` statics + // across all crates. || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) // Right now, the only way to get "foreign item symbol aliases" is by being an EII-implementation. diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 372829ea07a62..641d37e16f1b9 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -11,7 +11,6 @@ extern crate rustc_codegen_ssa; extern crate rustc_data_structures; extern crate rustc_driver; -extern crate rustc_hir; extern crate rustc_interface; extern crate rustc_log; extern crate rustc_middle; @@ -48,14 +47,9 @@ use miri::{ use rustc_codegen_ssa::traits::CodegenBackend; use rustc_data_structures::sync::{self, DynSync}; use rustc_driver::Compilation; -use rustc_hir::{self as hir, Node}; use rustc_interface::interface::Config; use rustc_interface::util::DummyCodegenBackend; use rustc_log::tracing::debug; -use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; -use rustc_middle::middle::exported_symbols::{ - ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel, -}; use rustc_middle::query::LocalCrate; use rustc_middle::ty::TyCtxt; use rustc_session::config::{CrateType, ErrorOutputType, OptLevel}; @@ -258,58 +252,14 @@ impl rustc_driver::Callbacks for MiriDepCompilerCalls { // Queries overridden here affect the data stored in `rmeta` files of dependencies, // which will be used later in non-`MIRI_BE_RUSTC` mode. config.override_queries = Some(|_, local_providers| { - // We need to add #[used] symbols to exported_symbols for `lookup_link_section`. - // FIXME handle this somehow in rustc itself to avoid this hack. - local_providers.queries.exported_non_generic_symbols = |tcx, LocalCrate| { - let reachable_set = tcx.with_stable_hashing_context(|mut hcx| { - tcx.reachable_set(()).to_sorted(&mut hcx, true) - }); - tcx.arena.alloc_from_iter( - // This is based on: - // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L62-L63 - // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L174 - reachable_set.into_iter().filter_map(|&local_def_id| { - // Do the same filtering that rustc does: - // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L84-L102 - // Otherwise it may cause unexpected behaviours and ICEs - // (https://github.com/rust-lang/rust/issues/86261). - let is_reachable_non_generic = matches!( - tcx.hir_node_by_def_id(local_def_id), - Node::Item(&hir::Item { - kind: hir::ItemKind::Static(..) | hir::ItemKind::Fn{ .. }, - .. - }) | Node::ImplItem(&hir::ImplItem { - kind: hir::ImplItemKind::Fn(..), - .. - }) - if !tcx.generics_of(local_def_id).requires_monomorphization(tcx) - ); - if !is_reachable_non_generic { - return None; - } - let codegen_fn_attrs = tcx.codegen_fn_attrs(local_def_id); - if codegen_fn_attrs.contains_extern_indicator() - || codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) - || codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) - { - Some(( - ExportedSymbol::NonGeneric(local_def_id.to_def_id()), - // Some dummy `SymbolExportInfo` here. We only use - // `exported_symbols` in shims/foreign_items.rs and the export info - // is ignored. - SymbolExportInfo { - level: SymbolExportLevel::C, - kind: SymbolExportKind::Text, - used: false, - rustc_std_internal_symbol: false, - }, - )) - } else { - None - } - }), - ) - } + // `exported_non_generic_symbols` is usually empty because we don't codegen anything. + // However, we need it for `lookup_link_section`. + // So overwrite the query with a version that dooes something even without codegen. + local_providers.queries.exported_non_generic_symbols = + |tcx, LocalCrate| rustc_codegen_ssa::back::exported_non_generic_symbols_helper(tcx); + // `exported_non_generic_symbols_helper` calls `reachable_non_generics`. + local_providers.queries.reachable_non_generics = + |tcx, LocalCrate| rustc_codegen_ssa::back::reachable_non_generics_helper(tcx); }); // Register our custom extra symbols. diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index c05c067d38b9d..8dc6b5f07b92e 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -116,34 +116,36 @@ pub fn path_ty_layout<'tcx>(cx: &impl LayoutOf<'tcx>, path: &[&str]) -> TyAndLay /// Call `f` for each exported symbol. pub fn iter_exported_symbols<'tcx>( tcx: TyCtxt<'tcx>, - mut f: impl FnMut(CrateNum, DefId) -> InterpResult<'tcx>, + mut f: impl FnMut(CrateNum, DefId, /* used */ bool) -> InterpResult<'tcx>, ) -> InterpResult<'tcx> { - // First, the symbols in the local crate. We can't use `exported_symbols` here as that - // skips `#[used]` statics (since `reachable_set` skips them in binary crates). - // So we walk all HIR items ourselves instead. + // First, the symbols in the local crate. We can't use `exported_symbols` here as that skips + // `#[used]` statics (since `reachable_set` does not specifically include them in binary crates, + // only in library crates). So we walk all HIR items ourselves instead. let crate_items = tcx.hir_crate_items(()); for def_id in crate_items.definitions() { - let exported = tcx.def_kind(def_id).has_codegen_attrs() && { - let codegen_attrs = tcx.codegen_fn_attrs(def_id); - codegen_attrs.contains_extern_indicator() - || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) - || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) - }; + if !tcx.def_kind(def_id).has_codegen_attrs() || tcx.is_foreign_item(def_id) { + continue; + } + let codegen_attrs = tcx.codegen_fn_attrs(def_id); + let used = codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) + || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER); + if !(used || codegen_attrs.contains_extern_indicator()) { + continue; + } // FIXME: `#[no_mangle]` makes no sense on a generic item, but still causes it to be // considered "extern". Remove this once `no_mangle_generic_items` is a hard error. - let exported_mono = exported && { + let mono = { let generics = tcx.generics_of(def_id); !generics.requires_monomorphization(tcx) }; - if exported_mono { - f(LOCAL_CRATE, def_id.into())?; + if mono { + f(LOCAL_CRATE, def_id.into(), used)?; } } // Next, all our dependencies. - // `dependency_formats` includes all the transitive information needed to link a crate, - // which is what we need here since we need to dig out `exported_symbols` from all transitive - // dependencies. + // `dependency_formats` includes all the transitive information needed to link a crate, which is + // what we need to dig out `exported_symbols` from all transitive dependencies. let dependency_formats = tcx.dependency_formats(()); // Find the dependencies of the executable we are running. let dependency_format = dependency_formats @@ -157,11 +159,12 @@ pub fn iter_exported_symbols<'tcx>( continue; // Already handled above } - // We can ignore `_export_info` here: we are a Rust crate, and everything is exported - // from a Rust crate. - for &(symbol, _export_info) in tcx.exported_non_generic_symbols(cnum) { - if let ExportedSymbol::NonGeneric(def_id) = symbol { - f(cnum, def_id)?; + for &(symbol, export_info) in tcx.exported_non_generic_symbols(cnum) { + if let ExportedSymbol::NonGeneric(def_id) = symbol + // Sometimes Rust has to re-export FFI imports; skip those. + && !tcx.is_foreign_item(def_id) + { + f(cnum, def_id, export_info.used)?; } } } @@ -961,8 +964,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let mut array = vec![]; - iter_exported_symbols(tcx, |_cnum, def_id| { + iter_exported_symbols(tcx, |_cnum, def_id, used| { let attrs = tcx.codegen_fn_attrs(def_id); + if !used { + // We don't know if the symbol is actually going to be in the final binary, + // so we conservatively skip it. + return interp_ok(()); + } let Some(link_section) = attrs.link_section else { return interp_ok(()); }; diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index efe597a5c1f7d..683e9095f9b0c 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -132,12 +132,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { is_weak: bool, } let mut symbol_target: Option> = None; - helpers::iter_exported_symbols(tcx, |cnum, def_id| { + helpers::iter_exported_symbols(tcx, |cnum, def_id, _used| { let attrs = tcx.codegen_fn_attrs(def_id); - // Skip over imports of items. - if tcx.is_foreign_item(def_id) { - return interp_ok(()); - } // Skip over items without an explicitly defined symbol name. if !(attrs.symbol_name.is_some() || attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) From c680b5097300ec4229f27fded5a14e177bf2eead Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 24 Jul 2026 12:14:27 +0200 Subject: [PATCH 25/63] Mention how the `-o` option behaves with the `--show-coverage` option --- src/doc/rustdoc/src/unstable-features.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 26985e67abb6e..9799f60c2802b 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -502,6 +502,15 @@ Calculating code examples follows these rules: * typedef 2. If one of the previously listed items has a code example, then it'll be counted. +If you use the `-o` option with it, it will generate the file into the given older name. For example: + +```shell +rustdoc foo.rs --show-coverage -o doc +``` + +Will generate a `foo.txt` into the `doc` folder. If the `-o` option isn't passed, it will display +on stdout. + ### JSON output When using `--output-format json` with this option, it will display the coverage information in From 4bd52e9b0997811bab7232d3789fdd5b6ab8989d Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:52:51 +0200 Subject: [PATCH 26/63] Remove redundant `#[rustc_paren_sugar]` feature gate --- .../src/attributes/traits.rs | 2 +- compiler/rustc_hir_analysis/src/collect.rs | 3 --- compiler/rustc_hir_analysis/src/diagnostics.rs | 10 ---------- .../feature-gate-unboxed-closures.rs | 4 ++++ .../feature-gate-unboxed-closures.stderr | 18 ++++++++++++++---- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/traits.rs b/compiler/rustc_attr_parsing/src/attributes/traits.rs index a2348eef975cf..69bdccb85c5cd 100644 --- a/compiler/rustc_attr_parsing/src/attributes/traits.rs +++ b/compiler/rustc_attr_parsing/src/attributes/traits.rs @@ -53,7 +53,7 @@ pub(crate) struct RustcParenSugarParser; impl NoArgsAttributeParser for RustcParenSugarParser { const PATH: &[Symbol] = &[sym::rustc_paren_sugar]; const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Trait)]); - const STABILITY: AttributeStability = unstable!(rustc_attrs); + const STABILITY: AttributeStability = unstable!(unboxed_closures); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcParenSugar; } diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 605b6e3751ef3..6c393ed3dfc6b 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -947,9 +947,6 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { let attrs = tcx.get_all_attrs(def_id); let paren_sugar = find_attr!(attrs, RustcParenSugar); - if paren_sugar && !tcx.features().unboxed_closures() { - tcx.dcx().emit_err(diagnostics::ParenSugarAttribute { span: item.span }); - } // Only regular traits can be marker. let is_marker = !is_alias && find_attr!(attrs, Marker); diff --git a/compiler/rustc_hir_analysis/src/diagnostics.rs b/compiler/rustc_hir_analysis/src/diagnostics.rs index a6e274ceb4bc6..0ea353cf14cee 100644 --- a/compiler/rustc_hir_analysis/src/diagnostics.rs +++ b/compiler/rustc_hir_analysis/src/diagnostics.rs @@ -862,16 +862,6 @@ pub(crate) struct EnumDiscriminantOverflowed { pub wrapped_discr: String, } -#[derive(Diagnostic)] -#[diag( - "the `#[rustc_paren_sugar]` attribute is a temporary means of controlling which traits can use parenthetical notation" -)] -#[help("add `#![feature(unboxed_closures)]` to the crate attributes to use it")] -pub(crate) struct ParenSugarAttribute { - #[primary_span] - pub span: Span, -} - #[derive(Diagnostic)] #[diag("use of SIMD type{$snip} in FFI is highly experimental and may result in invalid code")] #[help("add `#![feature(simd_ffi)]` to the crate attributes to enable")] diff --git a/tests/ui/feature-gates/feature-gate-unboxed-closures.rs b/tests/ui/feature-gates/feature-gate-unboxed-closures.rs index 74cc20c581cb9..fd4ea70bb350c 100644 --- a/tests/ui/feature-gates/feature-gate-unboxed-closures.rs +++ b/tests/ui/feature-gates/feature-gate-unboxed-closures.rs @@ -2,6 +2,10 @@ struct Test; + +#[rustc_paren_sugar] +//~^ ERROR the `rustc_paren_sugar` attribute is an experimental feature +pub trait Fn {} impl FnOnce<(u32, u32)> for Test { //~^ ERROR the precise format of `Fn`-family traits' type parameters is subject to change //~| ERROR manual implementations of `FnOnce` are experimental diff --git a/tests/ui/feature-gates/feature-gate-unboxed-closures.stderr b/tests/ui/feature-gates/feature-gate-unboxed-closures.stderr index 36932c1f86f9f..66a3a92c07901 100644 --- a/tests/ui/feature-gates/feature-gate-unboxed-closures.stderr +++ b/tests/ui/feature-gates/feature-gate-unboxed-closures.stderr @@ -1,5 +1,15 @@ +error[E0658]: the `rustc_paren_sugar` attribute is an experimental feature + --> $DIR/feature-gate-unboxed-closures.rs:6:3 + | +LL | #[rustc_paren_sugar] + | ^^^^^^^^^^^^^^^^^ + | + = note: see issue #29625 for more information + = help: add `#![feature(unboxed_closures)]` 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]: the extern "rust-call" ABI is experimental and subject to change - --> $DIR/feature-gate-unboxed-closures.rs:10:12 + --> $DIR/feature-gate-unboxed-closures.rs:14:12 | LL | extern "rust-call" fn call_once(self, (a, b): (u32, u32)) -> u32 { | ^^^^^^^^^^^ @@ -9,7 +19,7 @@ LL | extern "rust-call" fn call_once(self, (a, b): (u32, u32)) -> u32 { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change - --> $DIR/feature-gate-unboxed-closures.rs:5:6 + --> $DIR/feature-gate-unboxed-closures.rs:9:6 | LL | impl FnOnce<(u32, u32)> for Test { | ^^^^^^^^^^^^^^^^^^ @@ -19,14 +29,14 @@ LL | impl FnOnce<(u32, u32)> for Test { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0183]: manual implementations of `FnOnce` are experimental - --> $DIR/feature-gate-unboxed-closures.rs:5:6 + --> $DIR/feature-gate-unboxed-closures.rs:9:6 | LL | impl FnOnce<(u32, u32)> for Test { | ^^^^^^^^^^^^^^^^^^ manual implementations of `FnOnce` are experimental | = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable -error: aborting due to 3 previous errors +error: aborting due to 4 previous errors Some errors have detailed explanations: E0183, E0658. For more information about an error, try `rustc --explain E0183`. From 2dc6470711321dbc67d31aef0fa3c30743d3e012 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 20:29:02 +0200 Subject: [PATCH 27/63] add `FnHeader::span` --- compiler/rustc_ast/src/ast.rs | 46 +++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index d496c397395e9..9d5ab18c80ac3 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -2339,27 +2339,7 @@ pub struct FnSig { impl FnSig { /// Return a span encompassing the header, or where to insert it if empty. pub fn header_span(&self) -> Span { - match self.header.ext { - Extern::Implicit(span) | Extern::Explicit(_, span) => { - return self.span.with_hi(span.hi()); - } - Extern::None => {} - } - - match self.header.safety { - Safety::Unsafe(span) | Safety::Safe(span) => return self.span.with_hi(span.hi()), - Safety::Default => {} - }; - - if let Some(coroutine_kind) = self.header.coroutine_kind { - return self.span.with_hi(coroutine_kind.span().hi()); - } - - if let Const::Yes(span) = self.header.constness { - return self.span.with_hi(span.hi()); - } - - self.span.shrink_to_lo() + self.header.span().unwrap_or(self.span.shrink_to_lo()) } /// The span of the header's safety, or where to insert it if empty. @@ -3836,6 +3816,30 @@ impl FnHeader { || matches!(constness, Const::Yes(_)) || !matches!(ext, Extern::None) } + + pub fn span(&self) -> Option { + let mut spans = smallvec::SmallVec::<[Span; 4]>::new(); + + match self.ext { + Extern::Implicit(span) | Extern::Explicit(_, span) => spans.push(span), + Extern::None => {} + } + + match self.safety { + Safety::Unsafe(span) | Safety::Safe(span) => spans.push(span), + Safety::Default => {} + }; + + if let Some(coroutine_kind) = self.coroutine_kind { + spans.push(coroutine_kind.span()); + } + + if let Const::Yes(span) = self.constness { + spans.push(span) + } + + spans.into_iter().reduce(Span::to) + } } impl Default for FnHeader { From b16960fa934618b2810145ec8ca0c0148bc5ce9c Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 20:38:16 +0200 Subject: [PATCH 28/63] make `ident` optional in `check_extern_fn_signature` --- .../rustc_ast_passes/src/ast_validation.rs | 31 ++++++++++++++----- compiler/rustc_ast_passes/src/diagnostics.rs | 4 +-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index fe1da820cf74f..827f25a89d274 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -546,7 +546,13 @@ impl<'a> AstValidator<'a> { } /// Check that the signature of this function does not violate the constraints of its ABI. - fn check_extern_fn_signature(&self, abi: ExternAbi, ctxt: FnCtxt, ident: &Ident, sig: &FnSig) { + fn check_extern_fn_signature( + &self, + abi: ExternAbi, + ctxt: FnCtxt, + opt_function_name: Option<&Ident>, // None for function pointers + sig: &FnSig, + ) { match AbiMap::from_target(&self.sess.target).canonize_abi(abi, false) { AbiMapping::Direct(canon_abi) | AbiMapping::Deprecated(canon_abi) => { match canon_abi { @@ -575,7 +581,7 @@ impl<'a> AstValidator<'a> { self.reject_coroutine(abi, sig); // An `extern "custom"` function must have type `fn()`. - self.reject_params_or_return(abi, ident, sig); + self.reject_params_or_return(abi, opt_function_name, sig); } CanonAbi::Interrupt(interrupt_kind) => { @@ -600,7 +606,7 @@ impl<'a> AstValidator<'a> { self.reject_return(abi, sig); } else { // An `extern "interrupt"` function must have type `fn()`. - self.reject_params_or_return(abi, ident, sig); + self.reject_params_or_return(abi, opt_function_name, sig); } } } @@ -664,7 +670,12 @@ impl<'a> AstValidator<'a> { } } - fn reject_params_or_return(&self, abi: ExternAbi, ident: &Ident, sig: &FnSig) { + fn reject_params_or_return( + &self, + abi: ExternAbi, + opt_function_name: Option<&Ident>, // None for function pointers + sig: &FnSig, + ) { let mut spans: Vec<_> = sig.decl.inputs.iter().map(|p| p.span).collect(); if let FnRetTy::Ty(ref ret_ty) = sig.decl.output && match &ret_ty.kind { @@ -683,10 +694,14 @@ impl<'a> AstValidator<'a> { self.dcx().emit_err(diagnostics::AbiMustNotHaveParametersOrReturnType { spans, - symbol: ident.name, + abi, + suggestion_span, padding, - abi, + symbol: match opt_function_name { + Some(ident) => format!(" {}", ident.name), + None => String::new(), + }, }); } } @@ -1620,7 +1635,7 @@ impl Visitor<'_> for AstValidator<'_> { self.check_extern_fn_signature( self.extern_mod_abi.unwrap_or(ExternAbi::FALLBACK), FnCtxt::Foreign, - ident, + Some(ident), sig, ); @@ -1826,7 +1841,7 @@ impl Visitor<'_> for AstValidator<'_> { if let Some((extern_abi, extern_abi_span)) = ext { // Some ABIs impose special restrictions on the signature. - self.check_extern_fn_signature(extern_abi, ctxt, &fun.ident, &fun.sig); + self.check_extern_fn_signature(extern_abi, ctxt, Some(&fun.ident), &fun.sig); // #[track_caller] can only be used with the rust ABI. if let Some(attr) = attr::find_by_name(attrs, sym::track_caller) diff --git a/compiler/rustc_ast_passes/src/diagnostics.rs b/compiler/rustc_ast_passes/src/diagnostics.rs index 0cf7ef5c1de19..670cb4b66adff 100644 --- a/compiler/rustc_ast_passes/src/diagnostics.rs +++ b/compiler/rustc_ast_passes/src/diagnostics.rs @@ -1104,11 +1104,11 @@ pub(crate) struct AbiMustNotHaveParametersOrReturnType { #[suggestion( "remove the parameters and return type", applicability = "maybe-incorrect", - code = "{padding}fn {symbol}()", + code = "{padding}fn{symbol}()", style = "verbose" )] pub suggestion_span: Span, - pub symbol: Symbol, + pub symbol: String, pub padding: &'static str, } From 180f48d2cbb2e807aeacd29a69f33d8fb2b32bac Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 21:38:49 +0200 Subject: [PATCH 29/63] add `BorrowedFnSig` to prevent a clone down the road --- compiler/rustc_ast/src/ast.rs | 13 ++++++++++++ .../rustc_ast_passes/src/ast_validation.rs | 21 ++++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 9d5ab18c80ac3..927b081776b1b 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -2362,6 +2362,19 @@ impl FnSig { pub fn extern_span(&self) -> Span { self.header.ext.span().unwrap_or(self.safety_span().shrink_to_hi()) } + + pub fn as_borrowed(&self) -> BorrowedFnSig<'_> { + BorrowedFnSig { header: self.header, decl: &self.decl, span: self.span } + } +} + +/// A borrowed version of `FnSig`, used to share logic between function declarations and function +/// pointer types. +#[derive(Clone, Debug)] +pub struct BorrowedFnSig<'a> { + pub header: FnHeader, + pub decl: &'a FnDecl, + pub span: Span, } /// A constraint on an associated item. diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 827f25a89d274..c41563c08483a 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -551,7 +551,7 @@ impl<'a> AstValidator<'a> { abi: ExternAbi, ctxt: FnCtxt, opt_function_name: Option<&Ident>, // None for function pointers - sig: &FnSig, + sig: &BorrowedFnSig<'_>, ) { match AbiMap::from_target(&self.sess.target).canonize_abi(abi, false) { AbiMapping::Direct(canon_abi) | AbiMapping::Deprecated(canon_abi) => { @@ -615,7 +615,7 @@ impl<'a> AstValidator<'a> { } } - fn reject_safe_fn(&self, abi: ExternAbi, ctxt: FnCtxt, sig: &FnSig) { + fn reject_safe_fn(&self, abi: ExternAbi, ctxt: FnCtxt, sig: &BorrowedFnSig<'_>) { let dcx = self.dcx(); match sig.header.safety { @@ -641,7 +641,7 @@ impl<'a> AstValidator<'a> { } } - fn reject_coroutine(&self, abi: ExternAbi, sig: &FnSig) { + fn reject_coroutine(&self, abi: ExternAbi, sig: &BorrowedFnSig<'_>) { if let Some(coroutine_kind) = sig.header.coroutine_kind { let coroutine_kind_span = self .sess @@ -658,7 +658,7 @@ impl<'a> AstValidator<'a> { } } - fn reject_return(&self, abi: ExternAbi, sig: &FnSig) { + fn reject_return(&self, abi: ExternAbi, sig: &BorrowedFnSig<'_>) { if let FnRetTy::Ty(ref ret_ty) = sig.decl.output && match &ret_ty.kind { TyKind::Never => false, @@ -674,7 +674,7 @@ impl<'a> AstValidator<'a> { &self, abi: ExternAbi, opt_function_name: Option<&Ident>, // None for function pointers - sig: &FnSig, + sig: &BorrowedFnSig<'_>, ) { let mut spans: Vec<_> = sig.decl.inputs.iter().map(|p| p.span).collect(); if let FnRetTy::Ty(ref ret_ty) = sig.decl.output @@ -688,7 +688,7 @@ impl<'a> AstValidator<'a> { } if !spans.is_empty() { - let header_span = sig.header_span(); + let header_span = sig.header.span().unwrap_or(sig.span.shrink_to_lo()); let suggestion_span = header_span.shrink_to_hi().to(sig.decl.output.span()); let padding = if header_span.is_empty() { "" } else { " " }; @@ -1636,7 +1636,7 @@ impl Visitor<'_> for AstValidator<'_> { self.extern_mod_abi.unwrap_or(ExternAbi::FALLBACK), FnCtxt::Foreign, Some(ident), - sig, + &sig.as_borrowed(), ); if let Some(attr) = attr::find_by_name(fi.attrs(), sym::track_caller) @@ -1841,7 +1841,12 @@ impl Visitor<'_> for AstValidator<'_> { if let Some((extern_abi, extern_abi_span)) = ext { // Some ABIs impose special restrictions on the signature. - self.check_extern_fn_signature(extern_abi, ctxt, Some(&fun.ident), &fun.sig); + self.check_extern_fn_signature( + extern_abi, + ctxt, + Some(&fun.ident), + &fun.sig.as_borrowed(), + ); // #[track_caller] can only be used with the rust ABI. if let Some(attr) = attr::find_by_name(attrs, sym::track_caller) From f7a98cbff1b6b46785bf3a7d55c648a72d6928a8 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 20:39:31 +0200 Subject: [PATCH 30/63] add `FnPtrTy::header` --- compiler/rustc_ast/src/ast.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 927b081776b1b..30b973728b8f9 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -2480,6 +2480,12 @@ pub struct FnPtrTy { pub decl_span: Span, } +impl FnPtrTy { + pub fn header(&self) -> FnHeader { + FnHeader { constness: Const::No, coroutine_kind: None, safety: self.safety, ext: self.ext } + } +} + #[derive(Clone, Encodable, Decodable, Debug, Walkable)] pub struct UnsafeBinderTy { pub generic_params: ThinVec, From 152ac5846ab38128bb34c3fd9f7e66964563c2f8 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 20:45:27 +0200 Subject: [PATCH 31/63] run ABI checks on function pointer types and update the tests as needed. --- compiler/rustc_ast/src/ast.rs | 4 + .../rustc_ast_passes/src/ast_validation.rs | 18 +++++ tests/ui/abi/bad-custom.rs | 5 +- tests/ui/abi/bad-custom.stderr | 79 ++++++++++++++----- tests/ui/abi/cannot-be-called.amdgpu.stderr | 2 +- tests/ui/abi/cannot-be-called.avr.stderr | 2 +- tests/ui/abi/cannot-be-called.i686.stderr | 8 +- tests/ui/abi/cannot-be-called.msp430.stderr | 2 +- tests/ui/abi/cannot-be-called.nvptx.stderr | 2 +- tests/ui/abi/cannot-be-called.riscv32.stderr | 2 +- tests/ui/abi/cannot-be-called.riscv64.stderr | 2 +- tests/ui/abi/cannot-be-called.rs | 4 +- tests/ui/abi/cannot-be-called.x64.stderr | 8 +- tests/ui/abi/cannot-be-called.x64_win.stderr | 8 +- .../interrupt-invalid-signature.avr.stderr | 17 +++- .../interrupt-invalid-signature.i686.stderr | 52 ++++++++++-- .../interrupt-invalid-signature.msp430.stderr | 17 +++- ...interrupt-invalid-signature.riscv32.stderr | 32 +++++++- ...interrupt-invalid-signature.riscv64.stderr | 32 +++++++- tests/ui/abi/interrupt-invalid-signature.rs | 47 ++++++----- .../interrupt-invalid-signature.x64.stderr | 52 ++++++++++-- .../ui/abi/interrupt-returns-never-or-unit.rs | 33 ++++---- tests/ui/abi/unsupported.rs | 2 +- tests/ui/abi/unsupported.wasm32.stderr | 6 +- tests/ui/abi/unsupported.wasm64.stderr | 6 +- .../feature-gates/feature-gate-abi-custom.rs | 2 +- .../feature-gate-abi-custom.stderr | 6 +- .../feature-gate-abi-x86-interrupt.rs | 2 +- .../feature-gate-abi-x86-interrupt.stderr | 2 +- 29 files changed, 343 insertions(+), 111 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 30b973728b8f9..219e4bd00341b 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -2484,6 +2484,10 @@ impl FnPtrTy { pub fn header(&self) -> FnHeader { FnHeader { constness: Const::No, coroutine_kind: None, safety: self.safety, ext: self.ext } } + + pub fn as_borrowed_fn_sig<'a>(&'a self) -> BorrowedFnSig<'a> { + BorrowedFnSig { header: self.header(), decl: &self.decl, span: self.decl_span } + } } #[derive(Clone, Encodable, Decodable, Debug, Walkable)] diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index c41563c08483a..047212ac9d4ed 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -1153,6 +1153,24 @@ impl<'a> AstValidator<'a> { if let Extern::Implicit(extern_span) = bfty.ext { self.handle_missing_abi(extern_span, ty.id); } + + let ext = match bfty.ext { + Extern::None => None, + Extern::Implicit(_) => Some(ExternAbi::FALLBACK), + Extern::Explicit(str_lit, _) => { + ExternAbi::from_str(str_lit.symbol.as_str()).ok() + } + }; + + // Some ABIs impose special restrictions on the signature. + if let Some(extern_abi) = ext { + self.check_extern_fn_signature( + extern_abi, + FnCtxt::Free, + None, + &bfty.as_borrowed_fn_sig(), + ); + } } TyKind::TraitObject(bounds, ..) => { let mut any_lifetime_bounds = false; diff --git a/tests/ui/abi/bad-custom.rs b/tests/ui/abi/bad-custom.rs index 10e34fa19648a..cf026f7ae6536 100644 --- a/tests/ui/abi/bad-custom.rs +++ b/tests/ui/abi/bad-custom.rs @@ -72,16 +72,19 @@ unsafe extern "custom" { } fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { + //~^ ERROR invalid signature for `extern "custom"` function unsafe { f(x) } //~^ ERROR functions with the "custom" ABI cannot be called } fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { + //~^ ERROR invalid signature for `extern "custom"` function unsafe { f(x) } //~^ ERROR functions with the "custom" ABI cannot be called } type Custom = unsafe extern "custom" fn(i64) -> i64; +//~^ ERROR invalid signature for `extern "custom"` function fn caller_alias(f: Custom, mut x: i64) -> i64 { unsafe { f(x) } @@ -99,7 +102,7 @@ async unsafe extern "custom" fn no_async_fn() { //~| ERROR functions with the "custom" ABI cannot be `async` } -fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { +fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { //~^ ERROR expected an `Fn()` closure, found `unsafe extern "custom" fn()` f } diff --git a/tests/ui/abi/bad-custom.stderr b/tests/ui/abi/bad-custom.stderr index 46da91d80c993..9da50e93bd5ed 100644 --- a/tests/ui/abi/bad-custom.stderr +++ b/tests/ui/abi/bad-custom.stderr @@ -160,8 +160,47 @@ LL - safe fn extern_cannot_be_safe(); LL + fn extern_cannot_be_safe(); | +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:74:40 + | +LL | fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { + | ^^^ ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { +LL + fn caller(f: unsafe extern "custom" fn(), mut x: i64) -> i64 { + | + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:80:48 + | +LL | fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { + | ^^^ ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { +LL + fn caller_by_ref(f: &unsafe extern "custom" fn(), mut x: i64) -> i64 { + | + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:86:41 + | +LL | type Custom = unsafe extern "custom" fn(i64) -> i64; + | ^^^ ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - type Custom = unsafe extern "custom" fn(i64) -> i64; +LL + type Custom = unsafe extern "custom" fn(); + | + error: functions with the "custom" ABI cannot be `async` - --> $DIR/bad-custom.rs:97:1 + --> $DIR/bad-custom.rs:100:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -173,7 +212,7 @@ LL + unsafe extern "custom" fn no_async_fn() { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:97:1 + --> $DIR/bad-custom.rs:100:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -185,9 +224,9 @@ LL | async unsafe extern "custom" fn no_async_fn() { | error[E0277]: expected an `Fn()` closure, found `unsafe extern "custom" fn()` - --> $DIR/bad-custom.rs:102:64 + --> $DIR/bad-custom.rs:105:64 | -LL | fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { +LL | fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { | ^^^^^^^^^ call the function in a closure: `|| unsafe { /* code */ }` LL | LL | f @@ -246,96 +285,96 @@ LL | extern "custom" fn negate(a: i64) -> i64 { | error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:75:14 + --> $DIR/bad-custom.rs:76:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:75:14 + --> $DIR/bad-custom.rs:76:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:80:14 + --> $DIR/bad-custom.rs:82:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:80:14 + --> $DIR/bad-custom.rs:82:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:87:14 + --> $DIR/bad-custom.rs:90:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:87:14 + --> $DIR/bad-custom.rs:90:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:109:20 + --> $DIR/bad-custom.rs:112:20 | LL | assert_eq!(double(21), 42); | ^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:109:20 + --> $DIR/bad-custom.rs:112:20 | LL | assert_eq!(double(21), 42); | ^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:112:29 + --> $DIR/bad-custom.rs:115:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:112:29 + --> $DIR/bad-custom.rs:115:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:115:17 + --> $DIR/bad-custom.rs:118:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:115:17 + --> $DIR/bad-custom.rs:118:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:118:20 + --> $DIR/bad-custom.rs:121:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:118:20 + --> $DIR/bad-custom.rs:121:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ error[E0015]: inline assembly is not allowed in constant functions - --> $DIR/bad-custom.rs:93:5 + --> $DIR/bad-custom.rs:96:5 | LL | std::arch::naked_asm!("") | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 28 previous errors +error: aborting due to 31 previous errors Some errors have detailed explanations: E0015, E0277. For more information about an error, try `rustc --explain E0015`. diff --git a/tests/ui/abi/cannot-be-called.amdgpu.stderr b/tests/ui/abi/cannot-be-called.amdgpu.stderr index 039e2d7b4f009..b623d8e58d628 100644 --- a/tests/ui/abi/cannot-be-called.amdgpu.stderr +++ b/tests/ui/abi/cannot-be-called.amdgpu.stderr @@ -55,7 +55,7 @@ LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) { error[E0570]: "x86-interrupt" is not a supported ABI for the current target --> $DIR/cannot-be-called.rs:100:22 | -LL | fn x86_ptr(f: extern "x86-interrupt" fn()) { +LL | fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { | ^^^^^^^^^^^^^^^ error: functions with the "gpu-kernel" ABI cannot be called diff --git a/tests/ui/abi/cannot-be-called.avr.stderr b/tests/ui/abi/cannot-be-called.avr.stderr index 752292a58705f..1e18298ebdc69 100644 --- a/tests/ui/abi/cannot-be-called.avr.stderr +++ b/tests/ui/abi/cannot-be-called.avr.stderr @@ -49,7 +49,7 @@ LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) { error[E0570]: "x86-interrupt" is not a supported ABI for the current target --> $DIR/cannot-be-called.rs:100:22 | -LL | fn x86_ptr(f: extern "x86-interrupt" fn()) { +LL | fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { | ^^^^^^^^^^^^^^^ error[E0570]: "gpu-kernel" is not a supported ABI for the current target diff --git a/tests/ui/abi/cannot-be-called.i686.stderr b/tests/ui/abi/cannot-be-called.i686.stderr index 30e294ad61ecc..dfd9cbc47636f 100644 --- a/tests/ui/abi/cannot-be-called.i686.stderr +++ b/tests/ui/abi/cannot-be-called.i686.stderr @@ -73,14 +73,14 @@ LL | x86(&raw const BYTE); error: functions with the "x86-interrupt" ABI cannot be called --> $DIR/cannot-be-called.rs:102:5 | -LL | f() - | ^^^ +LL | f(frame) + | ^^^^^^^^ | note: an `extern "x86-interrupt"` function can only be called using inline assembly --> $DIR/cannot-be-called.rs:102:5 | -LL | f() - | ^^^ +LL | f(frame) + | ^^^^^^^^ error: aborting due to 12 previous errors diff --git a/tests/ui/abi/cannot-be-called.msp430.stderr b/tests/ui/abi/cannot-be-called.msp430.stderr index 12ab88bc858bd..0f3aa256d067f 100644 --- a/tests/ui/abi/cannot-be-called.msp430.stderr +++ b/tests/ui/abi/cannot-be-called.msp430.stderr @@ -49,7 +49,7 @@ LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) { error[E0570]: "x86-interrupt" is not a supported ABI for the current target --> $DIR/cannot-be-called.rs:100:22 | -LL | fn x86_ptr(f: extern "x86-interrupt" fn()) { +LL | fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { | ^^^^^^^^^^^^^^^ error[E0570]: "gpu-kernel" is not a supported ABI for the current target diff --git a/tests/ui/abi/cannot-be-called.nvptx.stderr b/tests/ui/abi/cannot-be-called.nvptx.stderr index 039e2d7b4f009..b623d8e58d628 100644 --- a/tests/ui/abi/cannot-be-called.nvptx.stderr +++ b/tests/ui/abi/cannot-be-called.nvptx.stderr @@ -55,7 +55,7 @@ LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) { error[E0570]: "x86-interrupt" is not a supported ABI for the current target --> $DIR/cannot-be-called.rs:100:22 | -LL | fn x86_ptr(f: extern "x86-interrupt" fn()) { +LL | fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { | ^^^^^^^^^^^^^^^ error: functions with the "gpu-kernel" ABI cannot be called diff --git a/tests/ui/abi/cannot-be-called.riscv32.stderr b/tests/ui/abi/cannot-be-called.riscv32.stderr index b1897b74ebbe3..f067d4aab6335 100644 --- a/tests/ui/abi/cannot-be-called.riscv32.stderr +++ b/tests/ui/abi/cannot-be-called.riscv32.stderr @@ -37,7 +37,7 @@ LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) { error[E0570]: "x86-interrupt" is not a supported ABI for the current target --> $DIR/cannot-be-called.rs:100:22 | -LL | fn x86_ptr(f: extern "x86-interrupt" fn()) { +LL | fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { | ^^^^^^^^^^^^^^^ error[E0570]: "gpu-kernel" is not a supported ABI for the current target diff --git a/tests/ui/abi/cannot-be-called.riscv64.stderr b/tests/ui/abi/cannot-be-called.riscv64.stderr index b1897b74ebbe3..f067d4aab6335 100644 --- a/tests/ui/abi/cannot-be-called.riscv64.stderr +++ b/tests/ui/abi/cannot-be-called.riscv64.stderr @@ -37,7 +37,7 @@ LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) { error[E0570]: "x86-interrupt" is not a supported ABI for the current target --> $DIR/cannot-be-called.rs:100:22 | -LL | fn x86_ptr(f: extern "x86-interrupt" fn()) { +LL | fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { | ^^^^^^^^^^^^^^^ error[E0570]: "gpu-kernel" is not a supported ABI for the current target diff --git a/tests/ui/abi/cannot-be-called.rs b/tests/ui/abi/cannot-be-called.rs index 8d1a4ae0b557f..eef2f8c671efa 100644 --- a/tests/ui/abi/cannot-be-called.rs +++ b/tests/ui/abi/cannot-be-called.rs @@ -97,9 +97,9 @@ fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) { //[riscv32,riscv64]~^ ERROR functions with the "riscv-interrupt-s" ABI cannot be called } -fn x86_ptr(f: extern "x86-interrupt" fn()) { +fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { //[riscv32,riscv64,avr,msp430,amdgpu,nvptx]~^ ERROR is not a supported ABI - f() + f(frame) //[x64,x64_win,i686]~^ ERROR functions with the "x86-interrupt" ABI cannot be called } diff --git a/tests/ui/abi/cannot-be-called.x64.stderr b/tests/ui/abi/cannot-be-called.x64.stderr index 30e294ad61ecc..dfd9cbc47636f 100644 --- a/tests/ui/abi/cannot-be-called.x64.stderr +++ b/tests/ui/abi/cannot-be-called.x64.stderr @@ -73,14 +73,14 @@ LL | x86(&raw const BYTE); error: functions with the "x86-interrupt" ABI cannot be called --> $DIR/cannot-be-called.rs:102:5 | -LL | f() - | ^^^ +LL | f(frame) + | ^^^^^^^^ | note: an `extern "x86-interrupt"` function can only be called using inline assembly --> $DIR/cannot-be-called.rs:102:5 | -LL | f() - | ^^^ +LL | f(frame) + | ^^^^^^^^ error: aborting due to 12 previous errors diff --git a/tests/ui/abi/cannot-be-called.x64_win.stderr b/tests/ui/abi/cannot-be-called.x64_win.stderr index 30e294ad61ecc..dfd9cbc47636f 100644 --- a/tests/ui/abi/cannot-be-called.x64_win.stderr +++ b/tests/ui/abi/cannot-be-called.x64_win.stderr @@ -73,14 +73,14 @@ LL | x86(&raw const BYTE); error: functions with the "x86-interrupt" ABI cannot be called --> $DIR/cannot-be-called.rs:102:5 | -LL | f() - | ^^^ +LL | f(frame) + | ^^^^^^^^ | note: an `extern "x86-interrupt"` function can only be called using inline assembly --> $DIR/cannot-be-called.rs:102:5 | -LL | f() - | ^^^ +LL | f(frame) + | ^^^^^^^^ error: aborting due to 12 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.avr.stderr b/tests/ui/abi/interrupt-invalid-signature.avr.stderr index 1cf91a33c523b..268d593b9120b 100644 --- a/tests/ui/abi/interrupt-invalid-signature.avr.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.avr.stderr @@ -12,7 +12,7 @@ LL + extern "avr-interrupt" fn avr_arg() {} | error: invalid signature for `extern "avr-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:60:40 + --> $DIR/interrupt-invalid-signature.rs:59:40 | LL | extern "avr-interrupt" fn avr_ret() -> u8 { | ^^ @@ -24,5 +24,18 @@ LL - extern "avr-interrupt" fn avr_ret() -> u8 { LL + extern "avr-interrupt" fn avr_ret() { | -error: aborting due to 2 previous errors +error: invalid signature for `extern "avr-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:107:42 + | +LL | fn avr_ptr(_f: extern "avr-interrupt" fn(u8) -> u8) { + | ^^ ^^ + | + = note: functions with the "avr-interrupt" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn avr_ptr(_f: extern "avr-interrupt" fn(u8) -> u8) { +LL + fn avr_ptr(_f: extern "avr-interrupt" fn()) { + | + +error: aborting due to 3 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.i686.stderr b/tests/ui/abi/interrupt-invalid-signature.i686.stderr index 3a8a0c057448b..257d7cf84940c 100644 --- a/tests/ui/abi/interrupt-invalid-signature.i686.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.i686.stderr @@ -1,18 +1,18 @@ error: invalid signature for `extern "x86-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:84:53 + --> $DIR/interrupt-invalid-signature.rs:83:53 | LL | extern "x86-interrupt" fn x86_ret(_p: *const u8) -> u8 { | ^^ | = note: functions with the "x86-interrupt" ABI cannot have a return type help: remove the return type - --> $DIR/interrupt-invalid-signature.rs:84:53 + --> $DIR/interrupt-invalid-signature.rs:83:53 | LL | extern "x86-interrupt" fn x86_ret(_p: *const u8) -> u8 { | ^^ error: invalid signature for `extern "x86-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:90:1 + --> $DIR/interrupt-invalid-signature.rs:89:1 | LL | extern "x86-interrupt" fn x86_0() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -20,12 +20,54 @@ LL | extern "x86-interrupt" fn x86_0() { = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 0) error: invalid signature for `extern "x86-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:101:33 + --> $DIR/interrupt-invalid-signature.rs:100:33 | LL | extern "x86-interrupt" fn x86_3(_p1: *const u8, _p2: *const u8, _p3: *const u8) { | ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ | = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 3) -error: aborting due to 3 previous errors +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:128:24 + | +LL | fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 0) + +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:128:55 + | +LL | fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + | ^^ + | + = note: functions with the "x86-interrupt" ABI cannot have a return type +help: remove the return type + --> $DIR/interrupt-invalid-signature.rs:128:55 + | +LL | fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + | ^^ + +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:135:51 + | +LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + | ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^ + | + = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 3) + +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:135:87 + | +LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + | ^^ + | + = note: functions with the "x86-interrupt" ABI cannot have a return type +help: remove the return type + --> $DIR/interrupt-invalid-signature.rs:135:87 + | +LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + | ^^ + +error: aborting due to 7 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.msp430.stderr b/tests/ui/abi/interrupt-invalid-signature.msp430.stderr index 3fff6492b7ea9..568c82e72acea 100644 --- a/tests/ui/abi/interrupt-invalid-signature.msp430.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.msp430.stderr @@ -12,7 +12,7 @@ LL + extern "msp430-interrupt" fn msp430_arg() {} | error: invalid signature for `extern "msp430-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:66:46 + --> $DIR/interrupt-invalid-signature.rs:65:46 | LL | extern "msp430-interrupt" fn msp430_ret() -> u8 { | ^^ @@ -24,5 +24,18 @@ LL - extern "msp430-interrupt" fn msp430_ret() -> u8 { LL + extern "msp430-interrupt" fn msp430_ret() { | -error: aborting due to 2 previous errors +error: invalid signature for `extern "msp430-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:112:48 + | +LL | fn msp430_ptr(_f: extern "msp430-interrupt" fn(u8) -> u8) { + | ^^ ^^ + | + = note: functions with the "msp430-interrupt" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn msp430_ptr(_f: extern "msp430-interrupt" fn(u8) -> u8) { +LL + fn msp430_ptr(_f: extern "msp430-interrupt" fn()) { + | + +error: aborting due to 3 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.riscv32.stderr b/tests/ui/abi/interrupt-invalid-signature.riscv32.stderr index 8aea68e65006c..21ec787da22ed 100644 --- a/tests/ui/abi/interrupt-invalid-signature.riscv32.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.riscv32.stderr @@ -25,7 +25,7 @@ LL + extern "riscv-interrupt-s" fn riscv_s_arg() {} | error: invalid signature for `extern "riscv-interrupt-m"` function - --> $DIR/interrupt-invalid-signature.rs:72:48 + --> $DIR/interrupt-invalid-signature.rs:71:48 | LL | extern "riscv-interrupt-m" fn riscv_m_ret() -> u8 { | ^^ @@ -38,7 +38,7 @@ LL + extern "riscv-interrupt-m" fn riscv_m_ret() { | error: invalid signature for `extern "riscv-interrupt-s"` function - --> $DIR/interrupt-invalid-signature.rs:78:48 + --> $DIR/interrupt-invalid-signature.rs:77:48 | LL | extern "riscv-interrupt-s" fn riscv_s_ret() -> u8 { | ^^ @@ -50,5 +50,31 @@ LL - extern "riscv-interrupt-s" fn riscv_s_ret() -> u8 { LL + extern "riscv-interrupt-s" fn riscv_s_ret() { | -error: aborting due to 4 previous errors +error: invalid signature for `extern "riscv-interrupt-m"` function + --> $DIR/interrupt-invalid-signature.rs:117:50 + | +LL | fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn(u8) -> u8) { + | ^^ ^^ + | + = note: functions with the "riscv-interrupt-m" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn(u8) -> u8) { +LL + fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn()) { + | + +error: invalid signature for `extern "riscv-interrupt-s"` function + --> $DIR/interrupt-invalid-signature.rs:122:50 + | +LL | fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn(u8) -> u8) { + | ^^ ^^ + | + = note: functions with the "riscv-interrupt-s" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn(u8) -> u8) { +LL + fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn()) { + | + +error: aborting due to 6 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.riscv64.stderr b/tests/ui/abi/interrupt-invalid-signature.riscv64.stderr index 8aea68e65006c..21ec787da22ed 100644 --- a/tests/ui/abi/interrupt-invalid-signature.riscv64.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.riscv64.stderr @@ -25,7 +25,7 @@ LL + extern "riscv-interrupt-s" fn riscv_s_arg() {} | error: invalid signature for `extern "riscv-interrupt-m"` function - --> $DIR/interrupt-invalid-signature.rs:72:48 + --> $DIR/interrupt-invalid-signature.rs:71:48 | LL | extern "riscv-interrupt-m" fn riscv_m_ret() -> u8 { | ^^ @@ -38,7 +38,7 @@ LL + extern "riscv-interrupt-m" fn riscv_m_ret() { | error: invalid signature for `extern "riscv-interrupt-s"` function - --> $DIR/interrupt-invalid-signature.rs:78:48 + --> $DIR/interrupt-invalid-signature.rs:77:48 | LL | extern "riscv-interrupt-s" fn riscv_s_ret() -> u8 { | ^^ @@ -50,5 +50,31 @@ LL - extern "riscv-interrupt-s" fn riscv_s_ret() -> u8 { LL + extern "riscv-interrupt-s" fn riscv_s_ret() { | -error: aborting due to 4 previous errors +error: invalid signature for `extern "riscv-interrupt-m"` function + --> $DIR/interrupt-invalid-signature.rs:117:50 + | +LL | fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn(u8) -> u8) { + | ^^ ^^ + | + = note: functions with the "riscv-interrupt-m" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn(u8) -> u8) { +LL + fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn()) { + | + +error: invalid signature for `extern "riscv-interrupt-s"` function + --> $DIR/interrupt-invalid-signature.rs:122:50 + | +LL | fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn(u8) -> u8) { + | ^^ ^^ + | + = note: functions with the "riscv-interrupt-s" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn(u8) -> u8) { +LL + fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn()) { + | + +error: aborting due to 6 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.rs b/tests/ui/abi/interrupt-invalid-signature.rs index 76302f6a4fad2..58aa72de561b5 100644 --- a/tests/ui/abi/interrupt-invalid-signature.rs +++ b/tests/ui/abi/interrupt-invalid-signature.rs @@ -45,15 +45,14 @@ extern "msp430-interrupt" fn msp430_arg(_byte: u8) {} extern "avr-interrupt" fn avr_arg(_byte: u8) {} //[avr]~^ ERROR invalid signature -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-m" fn riscv_m_arg(_byte: u8) {} //[riscv32,riscv64]~^ ERROR invalid signature -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-s" fn riscv_s_arg(_byte: u8) {} //[riscv32,riscv64]~^ ERROR invalid signature - /* all extern "interrupt" definitions should not return non-1ZST values */ #[cfg(avr)] @@ -68,60 +67,72 @@ extern "msp430-interrupt" fn msp430_ret() -> u8 { 1 } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-m" fn riscv_m_ret() -> u8 { //[riscv32,riscv64]~^ ERROR invalid signature 1 } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-s" fn riscv_s_ret() -> u8 { //[riscv32,riscv64]~^ ERROR invalid signature 1 } -#[cfg(any(x64,i686))] +#[cfg(any(x64, i686))] extern "x86-interrupt" fn x86_ret(_p: *const u8) -> u8 { //[x64,i686]~^ ERROR invalid signature 1 } -#[cfg(any(x64,i686))] +#[cfg(any(x64, i686))] extern "x86-interrupt" fn x86_0() { //[x64,i686]~^ ERROR invalid signature } -#[cfg(any(x64,i686))] -extern "x86-interrupt" fn x86_1(_p1: *const u8) { } +#[cfg(any(x64, i686))] +extern "x86-interrupt" fn x86_1(_p1: *const u8) {} -#[cfg(any(x64,i686))] -extern "x86-interrupt" fn x86_2(_p1: *const u8, _p2: *const u8) { } +#[cfg(any(x64, i686))] +extern "x86-interrupt" fn x86_2(_p1: *const u8, _p2: *const u8) {} -#[cfg(any(x64,i686))] +#[cfg(any(x64, i686))] extern "x86-interrupt" fn x86_3(_p1: *const u8, _p2: *const u8, _p3: *const u8) { //[x64,i686]~^ ERROR invalid signature } - - /* extern "interrupt" fnptrs with invalid signatures */ #[cfg(avr)] fn avr_ptr(_f: extern "avr-interrupt" fn(u8) -> u8) { + //[avr]~^ ERROR invalid signature } #[cfg(msp430)] fn msp430_ptr(_f: extern "msp430-interrupt" fn(u8) -> u8) { + //[msp430]~^ ERROR invalid signature } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn(u8) -> u8) { + //[riscv32,riscv64]~^ ERROR invalid signature } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn(u8) -> u8) { + //[riscv32,riscv64]~^ ERROR invalid signature } -#[cfg(any(x64,i686))] -fn x86_ptr(_f: extern "x86-interrupt" fn() -> u8) { +// One error for the argument list, another for the return type. +#[cfg(any(x64, i686))] +fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + //[x64,i686]~^ ERROR invalid signature + //[x64,i686]~| ERROR invalid signature +} + +// One error for the argument list, another for the return type. +#[cfg(any(x64, i686))] +fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + //[x64,i686]~^ ERROR invalid signature + //[x64,i686]~| ERROR invalid signature } diff --git a/tests/ui/abi/interrupt-invalid-signature.x64.stderr b/tests/ui/abi/interrupt-invalid-signature.x64.stderr index 3a8a0c057448b..257d7cf84940c 100644 --- a/tests/ui/abi/interrupt-invalid-signature.x64.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.x64.stderr @@ -1,18 +1,18 @@ error: invalid signature for `extern "x86-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:84:53 + --> $DIR/interrupt-invalid-signature.rs:83:53 | LL | extern "x86-interrupt" fn x86_ret(_p: *const u8) -> u8 { | ^^ | = note: functions with the "x86-interrupt" ABI cannot have a return type help: remove the return type - --> $DIR/interrupt-invalid-signature.rs:84:53 + --> $DIR/interrupt-invalid-signature.rs:83:53 | LL | extern "x86-interrupt" fn x86_ret(_p: *const u8) -> u8 { | ^^ error: invalid signature for `extern "x86-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:90:1 + --> $DIR/interrupt-invalid-signature.rs:89:1 | LL | extern "x86-interrupt" fn x86_0() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -20,12 +20,54 @@ LL | extern "x86-interrupt" fn x86_0() { = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 0) error: invalid signature for `extern "x86-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:101:33 + --> $DIR/interrupt-invalid-signature.rs:100:33 | LL | extern "x86-interrupt" fn x86_3(_p1: *const u8, _p2: *const u8, _p3: *const u8) { | ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ | = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 3) -error: aborting due to 3 previous errors +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:128:24 + | +LL | fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 0) + +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:128:55 + | +LL | fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + | ^^ + | + = note: functions with the "x86-interrupt" ABI cannot have a return type +help: remove the return type + --> $DIR/interrupt-invalid-signature.rs:128:55 + | +LL | fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + | ^^ + +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:135:51 + | +LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + | ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^ + | + = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 3) + +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:135:87 + | +LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + | ^^ + | + = note: functions with the "x86-interrupt" ABI cannot have a return type +help: remove the return type + --> $DIR/interrupt-invalid-signature.rs:135:87 + | +LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + | ^^ + +error: aborting due to 7 previous errors diff --git a/tests/ui/abi/interrupt-returns-never-or-unit.rs b/tests/ui/abi/interrupt-returns-never-or-unit.rs index 29713f2828ba2..75786730a2ca4 100644 --- a/tests/ui/abi/interrupt-returns-never-or-unit.rs +++ b/tests/ui/abi/interrupt-returns-never-or-unit.rs @@ -46,17 +46,17 @@ extern "msp430-interrupt" fn msp430_ret_never() -> ! { loop {} } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-m" fn riscv_m_ret_never() -> ! { loop {} } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-s" fn riscv_s_ret_never() -> ! { loop {} } -#[cfg(any(x64,i686))] +#[cfg(any(x64, i686))] extern "x86-interrupt" fn x86_ret_never(_p: *const u8) -> ! { loop {} } @@ -73,17 +73,17 @@ extern "msp430-interrupt" fn msp430_ret_unit() -> () { () } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-m" fn riscv_m_ret_unit() -> () { () } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-s" fn riscv_s_ret_unit() -> () { () } -#[cfg(any(x64,i686))] +#[cfg(any(x64, i686))] extern "x86-interrupt" fn x86_ret_unit(_x: *const u8) -> () { () } @@ -91,21 +91,16 @@ extern "x86-interrupt" fn x86_ret_unit(_x: *const u8) -> () { /* extern "interrupt" fnptrs can return ! too */ #[cfg(avr)] -fn avr_ptr(_f: extern "avr-interrupt" fn() -> !) { -} +fn avr_ptr(_f: extern "avr-interrupt" fn() -> !) {} #[cfg(msp430)] -fn msp430_ptr(_f: extern "msp430-interrupt" fn() -> !) { -} +fn msp430_ptr(_f: extern "msp430-interrupt" fn() -> !) {} -#[cfg(any(riscv32,riscv64))] -fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn() -> !) { -} +#[cfg(any(riscv32, riscv64))] +fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn() -> !) {} -#[cfg(any(riscv32,riscv64))] -fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn() -> !) { -} +#[cfg(any(riscv32, riscv64))] +fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn() -> !) {} -#[cfg(any(x64,i686))] -fn x86_ptr(_f: extern "x86-interrupt" fn() -> !) { -} +#[cfg(any(x64, i686))] +fn x86_ptr(_f: extern "x86-interrupt" fn(*const u8) -> !) {} diff --git a/tests/ui/abi/unsupported.rs b/tests/ui/abi/unsupported.rs index 661691956cb5d..a0b9e14b912e5 100644 --- a/tests/ui/abi/unsupported.rs +++ b/tests/ui/abi/unsupported.rs @@ -143,7 +143,7 @@ extern "cdecl" {} //[x64_win]~^ WARN unsupported_calling_conventions //[x64_win]~^^ WARN this was previously accepted -fn custom_ptr(f: extern "custom" fn()) { +fn custom_ptr(f: unsafe extern "custom" fn()) { //[wasm32,wasm64]~^ ERROR is not a supported ABI let _ = f; } diff --git a/tests/ui/abi/unsupported.wasm32.stderr b/tests/ui/abi/unsupported.wasm32.stderr index 9314c287b8591..826658ba7de87 100644 --- a/tests/ui/abi/unsupported.wasm32.stderr +++ b/tests/ui/abi/unsupported.wasm32.stderr @@ -157,10 +157,10 @@ LL | extern "cmse-nonsecure-entry" {} | ^^^^^^^^^^^^^^^^^^^^^^ error[E0570]: "custom" is not a supported ABI for the current target - --> $DIR/unsupported.rs:146:25 + --> $DIR/unsupported.rs:146:32 | -LL | fn custom_ptr(f: extern "custom" fn()) { - | ^^^^^^^^ +LL | fn custom_ptr(f: unsafe extern "custom" fn()) { + | ^^^^^^^^ error[E0570]: "custom" is not a supported ABI for the current target --> $DIR/unsupported.rs:150:8 diff --git a/tests/ui/abi/unsupported.wasm64.stderr b/tests/ui/abi/unsupported.wasm64.stderr index 9314c287b8591..826658ba7de87 100644 --- a/tests/ui/abi/unsupported.wasm64.stderr +++ b/tests/ui/abi/unsupported.wasm64.stderr @@ -157,10 +157,10 @@ LL | extern "cmse-nonsecure-entry" {} | ^^^^^^^^^^^^^^^^^^^^^^ error[E0570]: "custom" is not a supported ABI for the current target - --> $DIR/unsupported.rs:146:25 + --> $DIR/unsupported.rs:146:32 | -LL | fn custom_ptr(f: extern "custom" fn()) { - | ^^^^^^^^ +LL | fn custom_ptr(f: unsafe extern "custom" fn()) { + | ^^^^^^^^ error[E0570]: "custom" is not a supported ABI for the current target --> $DIR/unsupported.rs:150:8 diff --git a/tests/ui/feature-gates/feature-gate-abi-custom.rs b/tests/ui/feature-gates/feature-gate-abi-custom.rs index ca0b8337bbb75..f5e3c8e28b47c 100644 --- a/tests/ui/feature-gates/feature-gate-abi-custom.rs +++ b/tests/ui/feature-gates/feature-gate-abi-custom.rs @@ -46,6 +46,6 @@ impl S { } } -type A7 = extern "custom" fn(); //~ ERROR "custom" ABI is experimental +type A7 = unsafe extern "custom" fn(); //~ ERROR "custom" ABI is experimental extern "custom" {} //~ ERROR "custom" ABI is experimental diff --git a/tests/ui/feature-gates/feature-gate-abi-custom.stderr b/tests/ui/feature-gates/feature-gate-abi-custom.stderr index e359dbb5ebe1d..cee258d843b8e 100644 --- a/tests/ui/feature-gates/feature-gate-abi-custom.stderr +++ b/tests/ui/feature-gates/feature-gate-abi-custom.stderr @@ -93,10 +93,10 @@ LL | extern "custom" fn im7() { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: the extern "custom" ABI is experimental and subject to change - --> $DIR/feature-gate-abi-custom.rs:49:18 + --> $DIR/feature-gate-abi-custom.rs:49:25 | -LL | type A7 = extern "custom" fn(); - | ^^^^^^^^ +LL | type A7 = unsafe extern "custom" fn(); + | ^^^^^^^^ | = note: see issue #140829 for more information = help: add `#![feature(abi_custom)]` to the crate attributes to enable diff --git a/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.rs b/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.rs index 2bfe03b891108..2f5d01ed4cc99 100644 --- a/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.rs +++ b/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.rs @@ -25,6 +25,6 @@ impl S { extern "x86-interrupt" fn im7(_p: *const u8) {} //~ ERROR "x86-interrupt" ABI is experimental } -type A7 = extern "x86-interrupt" fn(); //~ ERROR "x86-interrupt" ABI is experimental +type A7 = extern "x86-interrupt" fn(*const u8); //~ ERROR "x86-interrupt" ABI is experimental extern "x86-interrupt" {} //~ ERROR "x86-interrupt" ABI is experimental diff --git a/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.stderr b/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.stderr index b53917dda610f..17b644b69a59e 100644 --- a/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.stderr +++ b/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.stderr @@ -51,7 +51,7 @@ LL | extern "x86-interrupt" fn im7(_p: *const u8) {} error[E0658]: the extern "x86-interrupt" ABI is experimental and subject to change --> $DIR/feature-gate-abi-x86-interrupt.rs:28:18 | -LL | type A7 = extern "x86-interrupt" fn(); +LL | type A7 = extern "x86-interrupt" fn(*const u8); | ^^^^^^^^^^^^^^^ | = note: see issue #40180 for more information From 55c729f65b5dfd07ebf871e59be15c27b4ff8fba Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 20:53:54 +0200 Subject: [PATCH 32/63] disallow `!` (i.e. `Never`) as a `extern "custom"` return type --- .../rustc_ast_passes/src/ast_validation.rs | 13 +-- tests/ui/abi/bad-custom.rs | 6 ++ tests/ui/abi/bad-custom.stderr | 83 +++++++++++-------- 3 files changed, 62 insertions(+), 40 deletions(-) diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 047212ac9d4ed..1e5fb328950d8 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -677,12 +677,15 @@ impl<'a> AstValidator<'a> { sig: &BorrowedFnSig<'_>, ) { let mut spans: Vec<_> = sig.decl.inputs.iter().map(|p| p.span).collect(); + + let allowed_return = |ret_ty: &Ty| match &ret_ty.kind { + TyKind::Never if abi != ExternAbi::Custom => true, + TyKind::Tup(tup) if tup.is_empty() => true, + _ => false, + }; + if let FnRetTy::Ty(ref ret_ty) = sig.decl.output - && match &ret_ty.kind { - TyKind::Never => false, - TyKind::Tup(tup) if tup.is_empty() => false, - _ => true, - } + && !allowed_return(ret_ty) { spans.push(ret_ty.span); } diff --git a/tests/ui/abi/bad-custom.rs b/tests/ui/abi/bad-custom.rs index cf026f7ae6536..dadeecd1a8572 100644 --- a/tests/ui/abi/bad-custom.rs +++ b/tests/ui/abi/bad-custom.rs @@ -22,6 +22,12 @@ unsafe extern "custom" fn no_return_type() -> i64 { std::arch::naked_asm!("") } +#[unsafe(naked)] +unsafe extern "custom" fn no_never_return_type() -> ! { + //~^ ERROR invalid signature for `extern "custom"` function + std::arch::naked_asm!("") +} + unsafe extern "custom" fn double(a: i64) -> i64 { //~^ ERROR items with the "custom" ABI can only be declared externally or defined via naked functions //~| ERROR invalid signature for `extern "custom"` function diff --git a/tests/ui/abi/bad-custom.stderr b/tests/ui/abi/bad-custom.stderr index 9da50e93bd5ed..bc375984403f6 100644 --- a/tests/ui/abi/bad-custom.stderr +++ b/tests/ui/abi/bad-custom.stderr @@ -49,7 +49,20 @@ LL + unsafe extern "custom" fn no_return_type() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:25:34 + --> $DIR/bad-custom.rs:26:53 + | +LL | unsafe extern "custom" fn no_never_return_type() -> ! { + | ^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - unsafe extern "custom" fn no_never_return_type() -> ! { +LL + unsafe extern "custom" fn no_never_return_type() { + | + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:31:34 | LL | unsafe extern "custom" fn double(a: i64) -> i64 { | ^^^^^^ ^^^ @@ -62,7 +75,7 @@ LL + unsafe extern "custom" fn double() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:34:39 + --> $DIR/bad-custom.rs:40:39 | LL | unsafe extern "custom" fn is_even(self) -> bool { | ^^^^ ^^^^ @@ -75,7 +88,7 @@ LL + unsafe extern "custom" fn is_even() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:42:43 + --> $DIR/bad-custom.rs:48:43 | LL | unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { | ^^^^^^ ^^^ @@ -88,7 +101,7 @@ LL + unsafe extern "custom" fn bitwise_not() { | error: functions with the "custom" ABI must be unsafe - --> $DIR/bad-custom.rs:52:5 + --> $DIR/bad-custom.rs:58:5 | LL | extern "custom" fn negate(a: i64) -> i64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -99,7 +112,7 @@ LL | unsafe extern "custom" fn negate(a: i64) -> i64; | ++++++ error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:52:31 + --> $DIR/bad-custom.rs:58:31 | LL | extern "custom" fn negate(a: i64) -> i64; | ^^^^^^ ^^^ @@ -112,7 +125,7 @@ LL + extern "custom" fn negate(); | error: functions with the "custom" ABI must be unsafe - --> $DIR/bad-custom.rs:58:5 + --> $DIR/bad-custom.rs:64:5 | LL | extern "custom" fn negate(a: i64) -> i64 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -123,7 +136,7 @@ LL | unsafe extern "custom" fn negate(a: i64) -> i64 { | ++++++ error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:58:31 + --> $DIR/bad-custom.rs:64:31 | LL | extern "custom" fn negate(a: i64) -> i64 { | ^^^^^^ ^^^ @@ -136,7 +149,7 @@ LL + extern "custom" fn negate() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:67:18 + --> $DIR/bad-custom.rs:73:18 | LL | fn increment(a: i64) -> i64; | ^^^^^^ ^^^ @@ -149,7 +162,7 @@ LL + fn increment(); | error: foreign functions with the "custom" ABI cannot be safe - --> $DIR/bad-custom.rs:70:5 + --> $DIR/bad-custom.rs:76:5 | LL | safe fn extern_cannot_be_safe(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -161,7 +174,7 @@ LL + fn extern_cannot_be_safe(); | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:74:40 + --> $DIR/bad-custom.rs:80:40 | LL | fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { | ^^^ ^^^ @@ -174,7 +187,7 @@ LL + fn caller(f: unsafe extern "custom" fn(), mut x: i64) -> i64 { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:80:48 + --> $DIR/bad-custom.rs:86:48 | LL | fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { | ^^^ ^^^ @@ -187,7 +200,7 @@ LL + fn caller_by_ref(f: &unsafe extern "custom" fn(), mut x: i64) -> i64 { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:86:41 + --> $DIR/bad-custom.rs:92:41 | LL | type Custom = unsafe extern "custom" fn(i64) -> i64; | ^^^ ^^^ @@ -200,7 +213,7 @@ LL + type Custom = unsafe extern "custom" fn(); | error: functions with the "custom" ABI cannot be `async` - --> $DIR/bad-custom.rs:100:1 + --> $DIR/bad-custom.rs:106:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -212,7 +225,7 @@ LL + unsafe extern "custom" fn no_async_fn() { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:100:1 + --> $DIR/bad-custom.rs:106:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -224,7 +237,7 @@ LL | async unsafe extern "custom" fn no_async_fn() { | error[E0277]: expected an `Fn()` closure, found `unsafe extern "custom" fn()` - --> $DIR/bad-custom.rs:105:64 + --> $DIR/bad-custom.rs:111:64 | LL | fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { | ^^^^^^^^^ call the function in a closure: `|| unsafe { /* code */ }` @@ -237,7 +250,7 @@ LL | f = note: unsafe function cannot be called generically without an unsafe block error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:25:1 + --> $DIR/bad-custom.rs:31:1 | LL | unsafe extern "custom" fn double(a: i64) -> i64 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -249,7 +262,7 @@ LL | unsafe extern "custom" fn double(a: i64) -> i64 { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:34:5 + --> $DIR/bad-custom.rs:40:5 | LL | unsafe extern "custom" fn is_even(self) -> bool { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -261,7 +274,7 @@ LL | unsafe extern "custom" fn is_even(self) -> bool { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:42:5 + --> $DIR/bad-custom.rs:48:5 | LL | unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -273,7 +286,7 @@ LL | unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:58:5 + --> $DIR/bad-custom.rs:64:5 | LL | extern "custom" fn negate(a: i64) -> i64 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -285,96 +298,96 @@ LL | extern "custom" fn negate(a: i64) -> i64 { | error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:76:14 + --> $DIR/bad-custom.rs:82:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:76:14 + --> $DIR/bad-custom.rs:82:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:82:14 + --> $DIR/bad-custom.rs:88:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:82:14 + --> $DIR/bad-custom.rs:88:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:90:14 + --> $DIR/bad-custom.rs:96:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:90:14 + --> $DIR/bad-custom.rs:96:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:112:20 + --> $DIR/bad-custom.rs:118:20 | LL | assert_eq!(double(21), 42); | ^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:112:20 + --> $DIR/bad-custom.rs:118:20 | LL | assert_eq!(double(21), 42); | ^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:115:29 + --> $DIR/bad-custom.rs:121:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:115:29 + --> $DIR/bad-custom.rs:121:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:118:17 + --> $DIR/bad-custom.rs:124:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:118:17 + --> $DIR/bad-custom.rs:124:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:121:20 + --> $DIR/bad-custom.rs:127:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:121:20 + --> $DIR/bad-custom.rs:127:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ error[E0015]: inline assembly is not allowed in constant functions - --> $DIR/bad-custom.rs:96:5 + --> $DIR/bad-custom.rs:102:5 | LL | std::arch::naked_asm!("") | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 31 previous errors +error: aborting due to 32 previous errors Some errors have detailed explanations: E0015, E0277. For more information about an error, try `rustc --explain E0015`. From a2c59e9d6c84c9c80b7f7c219157537fa3d29c07 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 20:54:57 +0200 Subject: [PATCH 33/63] test `extern "custom"` restrictions on function pointers --- tests/ui/abi/bad-custom.rs | 30 ++++- tests/ui/abi/bad-custom.stderr | 230 +++++++++++++++++++++++---------- 2 files changed, 188 insertions(+), 72 deletions(-) diff --git a/tests/ui/abi/bad-custom.rs b/tests/ui/abi/bad-custom.rs index dadeecd1a8572..a9e8ae2fd3bf8 100644 --- a/tests/ui/abi/bad-custom.rs +++ b/tests/ui/abi/bad-custom.rs @@ -10,44 +10,62 @@ extern "custom" fn must_be_unsafe(a: i64) -> i64 { std::arch::naked_asm!("") } +type MustbeUnsafe = extern "custom" fn(i64) -> i64; +//~^ ERROR functions with the "custom" ABI must be unsafe +//~| ERROR invalid signature for `extern "custom"` function + #[unsafe(naked)] unsafe extern "custom" fn no_parameters(a: i64) { //~^ ERROR invalid signature for `extern "custom"` function std::arch::naked_asm!("") } +type NoParameters = unsafe extern "custom" fn(i64); +//~^ ERROR invalid signature for `extern "custom"` function + #[unsafe(naked)] unsafe extern "custom" fn no_return_type() -> i64 { //~^ ERROR invalid signature for `extern "custom"` function std::arch::naked_asm!("") } +type NoReturnType = unsafe extern "custom" fn() -> i64; +//~^ ERROR invalid signature for `extern "custom"` function + #[unsafe(naked)] unsafe extern "custom" fn no_never_return_type() -> ! { //~^ ERROR invalid signature for `extern "custom"` function std::arch::naked_asm!("") } -unsafe extern "custom" fn double(a: i64) -> i64 { +type NoNeverReturnType = unsafe extern "custom" fn() -> !; +//~^ ERROR invalid signature for `extern "custom"` function + +unsafe extern "custom" fn not_both(a: i64) -> i64 { //~^ ERROR items with the "custom" ABI can only be declared externally or defined via naked functions //~| ERROR invalid signature for `extern "custom"` function unimplemented!() } +type NotBoth = unsafe extern "custom" fn(i64) -> i64; +//~^ ERROR invalid signature for `extern "custom"` function + struct Thing(i64); impl Thing { - unsafe extern "custom" fn is_even(self) -> bool { + extern "custom" fn is_even(self) -> bool { //~^ ERROR items with the "custom" ABI can only be declared externally or defined via naked functions //~| ERROR invalid signature for `extern "custom"` function + //~| ERROR functions with the "custom" ABI must be unsafe unimplemented!() } } trait BitwiseNot { - unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { + extern "custom" fn bitwise_not(a: i64) -> i64 { //~^ ERROR items with the "custom" ABI can only be declared externally or defined via naked functions //~| ERROR invalid signature for `extern "custom"` function + //~| ERROR functions with the "custom" ABI must be unsafe unimplemented!() } } @@ -89,10 +107,10 @@ fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { //~^ ERROR functions with the "custom" ABI cannot be called } -type Custom = unsafe extern "custom" fn(i64) -> i64; +type Alias = unsafe extern "custom" fn(i64) -> i64; //~^ ERROR invalid signature for `extern "custom"` function -fn caller_alias(f: Custom, mut x: i64) -> i64 { +fn caller_alias(f: Alias, mut x: i64) -> i64 { unsafe { f(x) } //~^ ERROR functions with the "custom" ABI cannot be called } @@ -115,7 +133,7 @@ fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { pub fn main() { unsafe { - assert_eq!(double(21), 42); + assert_eq!(not_both(21), 42); //~^ ERROR functions with the "custom" ABI cannot be called assert_eq!(unsafe { increment(41) }, 42); diff --git a/tests/ui/abi/bad-custom.stderr b/tests/ui/abi/bad-custom.stderr index bc375984403f6..1453382285624 100644 --- a/tests/ui/abi/bad-custom.stderr +++ b/tests/ui/abi/bad-custom.stderr @@ -22,8 +22,32 @@ LL - extern "custom" fn must_be_unsafe(a: i64) -> i64 { LL + extern "custom" fn must_be_unsafe() { | +error: functions with the "custom" ABI must be unsafe + --> $DIR/bad-custom.rs:13:21 + | +LL | type MustbeUnsafe = extern "custom" fn(i64) -> i64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: add the `unsafe` keyword to this definition + | +LL | type MustbeUnsafe = unsafe extern "custom" fn(i64) -> i64; + | ++++++ + error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:14:41 + --> $DIR/bad-custom.rs:13:40 + | +LL | type MustbeUnsafe = extern "custom" fn(i64) -> i64; + | ^^^ ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - type MustbeUnsafe = extern "custom" fn(i64) -> i64; +LL + type MustbeUnsafe = extern "custom" fn(); + | + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:18:41 | LL | unsafe extern "custom" fn no_parameters(a: i64) { | ^^^^^^ @@ -36,7 +60,20 @@ LL + unsafe extern "custom" fn no_parameters() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:20:47 + --> $DIR/bad-custom.rs:23:47 + | +LL | type NoParameters = unsafe extern "custom" fn(i64); + | ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - type NoParameters = unsafe extern "custom" fn(i64); +LL + type NoParameters = unsafe extern "custom" fn(); + | + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:27:47 | LL | unsafe extern "custom" fn no_return_type() -> i64 { | ^^^ @@ -49,7 +86,20 @@ LL + unsafe extern "custom" fn no_return_type() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:26:53 + --> $DIR/bad-custom.rs:32:52 + | +LL | type NoReturnType = unsafe extern "custom" fn() -> i64; + | ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - type NoReturnType = unsafe extern "custom" fn() -> i64; +LL + type NoReturnType = unsafe extern "custom" fn(); + | + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:36:53 | LL | unsafe extern "custom" fn no_never_return_type() -> ! { | ^ @@ -62,46 +112,94 @@ LL + unsafe extern "custom" fn no_never_return_type() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:31:34 + --> $DIR/bad-custom.rs:41:57 | -LL | unsafe extern "custom" fn double(a: i64) -> i64 { - | ^^^^^^ ^^^ +LL | type NoNeverReturnType = unsafe extern "custom" fn() -> !; + | ^ | = note: functions with the "custom" ABI cannot have any parameters or return type help: remove the parameters and return type | -LL - unsafe extern "custom" fn double(a: i64) -> i64 { -LL + unsafe extern "custom" fn double() { +LL - type NoNeverReturnType = unsafe extern "custom" fn() -> !; +LL + type NoNeverReturnType = unsafe extern "custom" fn(); | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:40:39 + --> $DIR/bad-custom.rs:44:36 | -LL | unsafe extern "custom" fn is_even(self) -> bool { - | ^^^^ ^^^^ +LL | unsafe extern "custom" fn not_both(a: i64) -> i64 { + | ^^^^^^ ^^^ | = note: functions with the "custom" ABI cannot have any parameters or return type help: remove the parameters and return type | -LL - unsafe extern "custom" fn is_even(self) -> bool { -LL + unsafe extern "custom" fn is_even() { +LL - unsafe extern "custom" fn not_both(a: i64) -> i64 { +LL + unsafe extern "custom" fn not_both() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:48:43 + --> $DIR/bad-custom.rs:50:42 + | +LL | type NotBoth = unsafe extern "custom" fn(i64) -> i64; + | ^^^ ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - type NotBoth = unsafe extern "custom" fn(i64) -> i64; +LL + type NotBoth = unsafe extern "custom" fn(); + | + +error: functions with the "custom" ABI must be unsafe + --> $DIR/bad-custom.rs:56:5 + | +LL | extern "custom" fn is_even(self) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: add the `unsafe` keyword to this definition + | +LL | unsafe extern "custom" fn is_even(self) -> bool { + | ++++++ + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:56:32 + | +LL | extern "custom" fn is_even(self) -> bool { + | ^^^^ ^^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - extern "custom" fn is_even(self) -> bool { +LL + extern "custom" fn is_even() { + | + +error: functions with the "custom" ABI must be unsafe + --> $DIR/bad-custom.rs:65:5 + | +LL | extern "custom" fn bitwise_not(a: i64) -> i64 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: add the `unsafe` keyword to this definition | LL | unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { - | ^^^^^^ ^^^ + | ++++++ + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:65:36 + | +LL | extern "custom" fn bitwise_not(a: i64) -> i64 { + | ^^^^^^ ^^^ | = note: functions with the "custom" ABI cannot have any parameters or return type help: remove the parameters and return type | -LL - unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { -LL + unsafe extern "custom" fn bitwise_not() { +LL - extern "custom" fn bitwise_not(a: i64) -> i64 { +LL + extern "custom" fn bitwise_not() { | error: functions with the "custom" ABI must be unsafe - --> $DIR/bad-custom.rs:58:5 + --> $DIR/bad-custom.rs:76:5 | LL | extern "custom" fn negate(a: i64) -> i64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -112,7 +210,7 @@ LL | unsafe extern "custom" fn negate(a: i64) -> i64; | ++++++ error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:58:31 + --> $DIR/bad-custom.rs:76:31 | LL | extern "custom" fn negate(a: i64) -> i64; | ^^^^^^ ^^^ @@ -125,7 +223,7 @@ LL + extern "custom" fn negate(); | error: functions with the "custom" ABI must be unsafe - --> $DIR/bad-custom.rs:64:5 + --> $DIR/bad-custom.rs:82:5 | LL | extern "custom" fn negate(a: i64) -> i64 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -136,7 +234,7 @@ LL | unsafe extern "custom" fn negate(a: i64) -> i64 { | ++++++ error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:64:31 + --> $DIR/bad-custom.rs:82:31 | LL | extern "custom" fn negate(a: i64) -> i64 { | ^^^^^^ ^^^ @@ -149,7 +247,7 @@ LL + extern "custom" fn negate() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:73:18 + --> $DIR/bad-custom.rs:91:18 | LL | fn increment(a: i64) -> i64; | ^^^^^^ ^^^ @@ -162,7 +260,7 @@ LL + fn increment(); | error: foreign functions with the "custom" ABI cannot be safe - --> $DIR/bad-custom.rs:76:5 + --> $DIR/bad-custom.rs:94:5 | LL | safe fn extern_cannot_be_safe(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -174,7 +272,7 @@ LL + fn extern_cannot_be_safe(); | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:80:40 + --> $DIR/bad-custom.rs:98:40 | LL | fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { | ^^^ ^^^ @@ -187,7 +285,7 @@ LL + fn caller(f: unsafe extern "custom" fn(), mut x: i64) -> i64 { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:86:48 + --> $DIR/bad-custom.rs:104:48 | LL | fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { | ^^^ ^^^ @@ -200,20 +298,20 @@ LL + fn caller_by_ref(f: &unsafe extern "custom" fn(), mut x: i64) -> i64 { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:92:41 + --> $DIR/bad-custom.rs:110:40 | -LL | type Custom = unsafe extern "custom" fn(i64) -> i64; - | ^^^ ^^^ +LL | type Alias = unsafe extern "custom" fn(i64) -> i64; + | ^^^ ^^^ | = note: functions with the "custom" ABI cannot have any parameters or return type help: remove the parameters and return type | -LL - type Custom = unsafe extern "custom" fn(i64) -> i64; -LL + type Custom = unsafe extern "custom" fn(); +LL - type Alias = unsafe extern "custom" fn(i64) -> i64; +LL + type Alias = unsafe extern "custom" fn(); | error: functions with the "custom" ABI cannot be `async` - --> $DIR/bad-custom.rs:106:1 + --> $DIR/bad-custom.rs:124:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -225,7 +323,7 @@ LL + unsafe extern "custom" fn no_async_fn() { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:106:1 + --> $DIR/bad-custom.rs:124:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -237,7 +335,7 @@ LL | async unsafe extern "custom" fn no_async_fn() { | error[E0277]: expected an `Fn()` closure, found `unsafe extern "custom" fn()` - --> $DIR/bad-custom.rs:111:64 + --> $DIR/bad-custom.rs:129:64 | LL | fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { | ^^^^^^^^^ call the function in a closure: `|| unsafe { /* code */ }` @@ -250,43 +348,43 @@ LL | f = note: unsafe function cannot be called generically without an unsafe block error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:31:1 + --> $DIR/bad-custom.rs:44:1 | -LL | unsafe extern "custom" fn double(a: i64) -> i64 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe extern "custom" fn not_both(a: i64) -> i64 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: convert this to an `#[unsafe(naked)]` function | LL + #[unsafe(naked)] -LL | unsafe extern "custom" fn double(a: i64) -> i64 { +LL | unsafe extern "custom" fn not_both(a: i64) -> i64 { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:40:5 + --> $DIR/bad-custom.rs:56:5 | -LL | unsafe extern "custom" fn is_even(self) -> bool { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | extern "custom" fn is_even(self) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: convert this to an `#[unsafe(naked)]` function | LL + #[unsafe(naked)] -LL | unsafe extern "custom" fn is_even(self) -> bool { +LL | extern "custom" fn is_even(self) -> bool { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:48:5 + --> $DIR/bad-custom.rs:65:5 | -LL | unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | extern "custom" fn bitwise_not(a: i64) -> i64 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: convert this to an `#[unsafe(naked)]` function | LL + #[unsafe(naked)] -LL | unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { +LL | extern "custom" fn bitwise_not(a: i64) -> i64 { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:64:5 + --> $DIR/bad-custom.rs:82:5 | LL | extern "custom" fn negate(a: i64) -> i64 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -298,96 +396,96 @@ LL | extern "custom" fn negate(a: i64) -> i64 { | error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:82:14 + --> $DIR/bad-custom.rs:100:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:82:14 + --> $DIR/bad-custom.rs:100:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:88:14 + --> $DIR/bad-custom.rs:106:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:88:14 + --> $DIR/bad-custom.rs:106:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:96:14 + --> $DIR/bad-custom.rs:114:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:96:14 + --> $DIR/bad-custom.rs:114:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:118:20 + --> $DIR/bad-custom.rs:136:20 | -LL | assert_eq!(double(21), 42); - | ^^^^^^^^^^ +LL | assert_eq!(not_both(21), 42); + | ^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:118:20 + --> $DIR/bad-custom.rs:136:20 | -LL | assert_eq!(double(21), 42); - | ^^^^^^^^^^ +LL | assert_eq!(not_both(21), 42); + | ^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:121:29 + --> $DIR/bad-custom.rs:139:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:121:29 + --> $DIR/bad-custom.rs:139:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:124:17 + --> $DIR/bad-custom.rs:142:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:124:17 + --> $DIR/bad-custom.rs:142:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:127:20 + --> $DIR/bad-custom.rs:145:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:127:20 + --> $DIR/bad-custom.rs:145:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ error[E0015]: inline assembly is not allowed in constant functions - --> $DIR/bad-custom.rs:102:5 + --> $DIR/bad-custom.rs:120:5 | LL | std::arch::naked_asm!("") | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 32 previous errors +error: aborting due to 40 previous errors Some errors have detailed explanations: E0015, E0277. For more information about an error, try `rustc --explain E0015`. From d0dde6040c77f86ec06421f6a5e2819c02e06972 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 21:58:54 +0200 Subject: [PATCH 34/63] improve suggestion when `safe` is used on function pointers --- .../rustc_ast_passes/src/ast_validation.rs | 33 +++++++---- compiler/rustc_ast_passes/src/diagnostics.rs | 7 +++ tests/ui/abi/bad-custom.rs | 3 + tests/ui/abi/bad-custom.stderr | 56 +++++++++++-------- .../interrupt-invalid-signature.avr.stderr | 14 ++++- .../interrupt-invalid-signature.i686.stderr | 14 ++++- tests/ui/abi/interrupt-invalid-signature.rs | 8 +++ .../interrupt-invalid-signature.x64.stderr | 14 ++++- tests/ui/rust-2024/safe-outside-extern.stderr | 8 ++- 9 files changed, 121 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 1e5fb328950d8..16516347a1f42 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -575,7 +575,7 @@ impl<'a> AstValidator<'a> { CanonAbi::Custom => { // An `extern "custom"` function must be unsafe. - self.reject_safe_fn(abi, ctxt, sig); + self.reject_safe_fn(abi, ctxt, sig, opt_function_name.is_none()); // An `extern "custom"` function cannot be `async` and/or `gen`. self.reject_coroutine(abi, sig); @@ -615,18 +615,27 @@ impl<'a> AstValidator<'a> { } } - fn reject_safe_fn(&self, abi: ExternAbi, ctxt: FnCtxt, sig: &BorrowedFnSig<'_>) { + fn reject_safe_fn( + &self, + abi: ExternAbi, + ctxt: FnCtxt, + sig: &BorrowedFnSig<'_>, + is_fn_ptr: bool, + ) { let dcx = self.dcx(); match sig.header.safety { Safety::Unsafe(_) => { /* all good */ } Safety::Safe(safe_span) => { - let source_map = self.sess.psess.source_map(); - let safe_span = source_map.span_until_non_whitespace(safe_span.to(sig.span)); - dcx.emit_err(diagnostics::AbiCustomSafeForeignFunction { - span: sig.span, - safe_span, - }); + // Function pointers already error when `safe` is used. + if !is_fn_ptr { + let source_map = self.sess.psess.source_map(); + let safe_span = source_map.span_until_non_whitespace(safe_span.to(sig.span)); + dcx.emit_err(diagnostics::AbiCustomSafeForeignFunction { + span: sig.span, + safe_span, + }); + } } Safety::Default => match ctxt { FnCtxt::Foreign => { /* all good */ } @@ -735,8 +744,12 @@ impl<'a> AstValidator<'a> { } fn check_fn_ptr_safety(&self, span: Span, safety: Safety) { - if matches!(safety, Safety::Safe(_)) { - self.dcx().emit_err(diagnostics::InvalidSafetyOnFnPtr { span }); + if let Safety::Safe(safe_span) = safety { + let remove_span = self.sess.source_map().span_until_non_whitespace(span); + self.dcx().emit_err(diagnostics::InvalidSafetyOnFnPtr { + span: safe_span, + safe_span: remove_span, + }); } } diff --git a/compiler/rustc_ast_passes/src/diagnostics.rs b/compiler/rustc_ast_passes/src/diagnostics.rs index 670cb4b66adff..60660dce8e5ea 100644 --- a/compiler/rustc_ast_passes/src/diagnostics.rs +++ b/compiler/rustc_ast_passes/src/diagnostics.rs @@ -371,6 +371,13 @@ pub(crate) struct InvalidSafetyOnItem { pub(crate) struct InvalidSafetyOnFnPtr { #[primary_span] pub span: Span, + #[suggestion( + "remove the `safe` qualifier", + code = "", + applicability = "machine-applicable", + style = "verbose" + )] + pub safe_span: Span, } #[derive(Diagnostic)] diff --git a/tests/ui/abi/bad-custom.rs b/tests/ui/abi/bad-custom.rs index a9e8ae2fd3bf8..793ef4d5dd3ea 100644 --- a/tests/ui/abi/bad-custom.rs +++ b/tests/ui/abi/bad-custom.rs @@ -95,6 +95,9 @@ unsafe extern "custom" { //~^ ERROR foreign functions with the "custom" ABI cannot be safe } +type Safe = safe extern "custom" fn(); +//~^ ERROR function pointers cannot be declared with `safe` safety qualifier + fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { //~^ ERROR invalid signature for `extern "custom"` function unsafe { f(x) } diff --git a/tests/ui/abi/bad-custom.stderr b/tests/ui/abi/bad-custom.stderr index 1453382285624..8aea96fd7257e 100644 --- a/tests/ui/abi/bad-custom.stderr +++ b/tests/ui/abi/bad-custom.stderr @@ -271,8 +271,20 @@ LL - safe fn extern_cannot_be_safe(); LL + fn extern_cannot_be_safe(); | +error: function pointers cannot be declared with `safe` safety qualifier + --> $DIR/bad-custom.rs:98:13 + | +LL | type Safe = safe extern "custom" fn(); + | ^^^^ + | +help: remove the `safe` qualifier + | +LL - type Safe = safe extern "custom" fn(); +LL + type Safe = extern "custom" fn(); + | + error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:98:40 + --> $DIR/bad-custom.rs:101:40 | LL | fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { | ^^^ ^^^ @@ -285,7 +297,7 @@ LL + fn caller(f: unsafe extern "custom" fn(), mut x: i64) -> i64 { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:104:48 + --> $DIR/bad-custom.rs:107:48 | LL | fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { | ^^^ ^^^ @@ -298,7 +310,7 @@ LL + fn caller_by_ref(f: &unsafe extern "custom" fn(), mut x: i64) -> i64 { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:110:40 + --> $DIR/bad-custom.rs:113:40 | LL | type Alias = unsafe extern "custom" fn(i64) -> i64; | ^^^ ^^^ @@ -311,7 +323,7 @@ LL + type Alias = unsafe extern "custom" fn(); | error: functions with the "custom" ABI cannot be `async` - --> $DIR/bad-custom.rs:124:1 + --> $DIR/bad-custom.rs:127:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -323,7 +335,7 @@ LL + unsafe extern "custom" fn no_async_fn() { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:124:1 + --> $DIR/bad-custom.rs:127:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -335,7 +347,7 @@ LL | async unsafe extern "custom" fn no_async_fn() { | error[E0277]: expected an `Fn()` closure, found `unsafe extern "custom" fn()` - --> $DIR/bad-custom.rs:129:64 + --> $DIR/bad-custom.rs:132:64 | LL | fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { | ^^^^^^^^^ call the function in a closure: `|| unsafe { /* code */ }` @@ -396,96 +408,96 @@ LL | extern "custom" fn negate(a: i64) -> i64 { | error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:100:14 + --> $DIR/bad-custom.rs:103:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:100:14 + --> $DIR/bad-custom.rs:103:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:106:14 + --> $DIR/bad-custom.rs:109:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:106:14 + --> $DIR/bad-custom.rs:109:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:114:14 + --> $DIR/bad-custom.rs:117:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:114:14 + --> $DIR/bad-custom.rs:117:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:136:20 + --> $DIR/bad-custom.rs:139:20 | LL | assert_eq!(not_both(21), 42); | ^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:136:20 + --> $DIR/bad-custom.rs:139:20 | LL | assert_eq!(not_both(21), 42); | ^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:139:29 + --> $DIR/bad-custom.rs:142:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:139:29 + --> $DIR/bad-custom.rs:142:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:142:17 + --> $DIR/bad-custom.rs:145:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:142:17 + --> $DIR/bad-custom.rs:145:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:145:20 + --> $DIR/bad-custom.rs:148:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:145:20 + --> $DIR/bad-custom.rs:148:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ error[E0015]: inline assembly is not allowed in constant functions - --> $DIR/bad-custom.rs:120:5 + --> $DIR/bad-custom.rs:123:5 | LL | std::arch::naked_asm!("") | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 40 previous errors +error: aborting due to 41 previous errors Some errors have detailed explanations: E0015, E0277. For more information about an error, try `rustc --explain E0015`. diff --git a/tests/ui/abi/interrupt-invalid-signature.avr.stderr b/tests/ui/abi/interrupt-invalid-signature.avr.stderr index 268d593b9120b..7830999260ad6 100644 --- a/tests/ui/abi/interrupt-invalid-signature.avr.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.avr.stderr @@ -37,5 +37,17 @@ LL - fn avr_ptr(_f: extern "avr-interrupt" fn(u8) -> u8) { LL + fn avr_ptr(_f: extern "avr-interrupt" fn()) { | -error: aborting due to 3 previous errors +error: function pointers cannot be declared with `safe` safety qualifier + --> $DIR/interrupt-invalid-signature.rs:141:13 + | +LL | type Safe = safe extern "avr-interrupt" fn(); + | ^^^^ + | +help: remove the `safe` qualifier + | +LL - type Safe = safe extern "avr-interrupt" fn(); +LL + type Safe = extern "avr-interrupt" fn(); + | + +error: aborting due to 4 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.i686.stderr b/tests/ui/abi/interrupt-invalid-signature.i686.stderr index 257d7cf84940c..7e28fbf1f3822 100644 --- a/tests/ui/abi/interrupt-invalid-signature.i686.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.i686.stderr @@ -69,5 +69,17 @@ help: remove the return type LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { | ^^ -error: aborting due to 7 previous errors +error: function pointers cannot be declared with `safe` safety qualifier + --> $DIR/interrupt-invalid-signature.rs:145:13 + | +LL | type Safe = safe extern "x86-interrupt" fn(*const u8); + | ^^^^ + | +help: remove the `safe` qualifier + | +LL - type Safe = safe extern "x86-interrupt" fn(*const u8); +LL + type Safe = extern "x86-interrupt" fn(*const u8); + | + +error: aborting due to 8 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.rs b/tests/ui/abi/interrupt-invalid-signature.rs index 58aa72de561b5..083d93fef0774 100644 --- a/tests/ui/abi/interrupt-invalid-signature.rs +++ b/tests/ui/abi/interrupt-invalid-signature.rs @@ -136,3 +136,11 @@ fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u //[x64,i686]~^ ERROR invalid signature //[x64,i686]~| ERROR invalid signature } + +#[cfg(avr)] +type Safe = safe extern "avr-interrupt" fn(); +//[avr]~^ ERROR function pointers cannot be declared with `safe` safety qualifier + +#[cfg(any(x64, i686))] +type Safe = safe extern "x86-interrupt" fn(*const u8); +//[x64,i686]~^ ERROR function pointers cannot be declared with `safe` safety qualifier diff --git a/tests/ui/abi/interrupt-invalid-signature.x64.stderr b/tests/ui/abi/interrupt-invalid-signature.x64.stderr index 257d7cf84940c..7e28fbf1f3822 100644 --- a/tests/ui/abi/interrupt-invalid-signature.x64.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.x64.stderr @@ -69,5 +69,17 @@ help: remove the return type LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { | ^^ -error: aborting due to 7 previous errors +error: function pointers cannot be declared with `safe` safety qualifier + --> $DIR/interrupt-invalid-signature.rs:145:13 + | +LL | type Safe = safe extern "x86-interrupt" fn(*const u8); + | ^^^^ + | +help: remove the `safe` qualifier + | +LL - type Safe = safe extern "x86-interrupt" fn(*const u8); +LL + type Safe = extern "x86-interrupt" fn(*const u8); + | + +error: aborting due to 8 previous errors diff --git a/tests/ui/rust-2024/safe-outside-extern.stderr b/tests/ui/rust-2024/safe-outside-extern.stderr index 19d7c5fde0bdf..1e46502df954e 100644 --- a/tests/ui/rust-2024/safe-outside-extern.stderr +++ b/tests/ui/rust-2024/safe-outside-extern.stderr @@ -26,7 +26,13 @@ error: function pointers cannot be declared with `safe` safety qualifier --> $DIR/safe-outside-extern.rs:17:14 | LL | type FnPtr = safe fn(i32, i32) -> i32; - | ^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^ + | +help: remove the `safe` qualifier + | +LL - type FnPtr = safe fn(i32, i32) -> i32; +LL + type FnPtr = fn(i32, i32) -> i32; + | error: static items cannot be declared with `unsafe` safety qualifier outside of `extern` block --> $DIR/safe-outside-extern.rs:20:1 From a32fa442efe45fb9af7a5260b63ede856edc8e7f Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Fri, 24 Jul 2026 12:55:03 -0400 Subject: [PATCH 35/63] Updated expect messages for CString struct and method documentation --- library/alloc/src/ffi/c_str.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/library/alloc/src/ffi/c_str.rs b/library/alloc/src/ffi/c_str.rs index e6e6fcf5420f3..b340cf9566f2e 100644 --- a/library/alloc/src/ffi/c_str.rs +++ b/library/alloc/src/ffi/c_str.rs @@ -84,7 +84,7 @@ use crate::vec::Vec; /// /// // We are certain that our string doesn't have 0 bytes in the middle, /// // so we can .expect() -/// let c_to_print = CString::new("Hello, world!").expect("CString::new failed"); +/// let c_to_print = CString::new("Hello, world!").expect("we provided a string without NUL bytes, so CString::new should not fail"); /// unsafe { /// my_printer(c_to_print.as_ptr()); /// } @@ -242,7 +242,7 @@ impl CString { /// /// extern "C" { fn puts(s: *const c_char); } /// - /// let to_print = CString::new("Hello!").expect("CString::new failed"); + /// let to_print = CString::new("Hello!").expect("we provided a string without NUL bytes, so CString::new should not fail"); /// unsafe { /// puts(to_print.as_ptr()); /// } @@ -466,12 +466,12 @@ impl CString { /// use std::ffi::CString; /// /// let valid_utf8 = vec![b'f', b'o', b'o']; - /// let cstring = CString::new(valid_utf8).expect("CString::new failed"); - /// assert_eq!(cstring.into_string().expect("into_string() call failed"), "foo"); + /// let cstring = CString::new(valid_utf8).expect("we provided bytes that do not have a NUL byte, so CString::new should not fail"); + /// assert_eq!(cstring.into_string().expect("we provided bytes that are valid UTF-8, so `into_string` should not fail"), "foo"); /// /// let invalid_utf8 = vec![b'f', 0xff, b'o', b'o']; - /// let cstring = CString::new(invalid_utf8).expect("CString::new failed"); - /// let err = cstring.into_string().err().expect("into_string().err() failed"); + /// let cstring = CString::new(invalid_utf8).expect("we provided bytes that do not have a NUL byte, so CString::new should not fail"); + /// let err = cstring.into_string().expect_err("we provided bytes that are invalid UTF-8, so `into_string` should fail"); /// assert_eq!(err.utf8_error().valid_up_to(), 1); /// ``` #[stable(feature = "cstring_into", since = "1.7.0")] @@ -577,7 +577,7 @@ impl CString { /// let c_string = CString::from(c"foo"); /// let cstr = c_string.as_c_str(); /// assert_eq!(cstr, - /// CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed")); + /// CStr::from_bytes_with_nul(b"foo\0").expect("we provided bytes that has one NUL byte exactly at the end, so CStr::from_bytes_with_nul should not fail")); /// ``` #[inline] #[must_use] @@ -660,7 +660,7 @@ impl CString { /// use std::ffi::CString; /// assert_eq!( /// CString::from_vec_with_nul(b"abc\0".to_vec()) - /// .expect("CString::from_vec_with_nul failed"), + /// .expect("we provided bytes that has one NUL byte exactly at the end, so CString::from_vec_with_nul should not fail"), /// c"abc".to_owned() /// ); /// ``` From c659cbde30fa37272cf9ab8d00cb05d533a7a06d Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Sat, 25 Jul 2026 02:38:59 +0000 Subject: [PATCH 36/63] Revert "Export `derive` at `core::derive` and `std::derive`" This reverts commit fec09987324f37dda32c79f12d2d8c342805ae11. --- library/core/src/lib.rs | 2 -- library/core/src/macros/mod.rs | 2 +- library/core/src/prelude/v1.rs | 6 +----- library/std/src/lib.rs | 2 -- library/std/src/prelude/v1.rs | 6 +----- tests/ui/imports/global-derive-path.rs | 10 ---------- 6 files changed, 3 insertions(+), 25 deletions(-) delete mode 100644 tests/ui/imports/global-derive-path.rs diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 6e670375fcc5e..c3f52d040d4c5 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -226,8 +226,6 @@ pub mod offload; #[unstable(feature = "contracts", issue = "128044")] pub mod contracts; -#[unstable(feature = "derive_macro_global_path", issue = "154645")] -pub use crate::macros::builtin::derive; #[stable(feature = "cfg_select", since = "1.95.0")] pub use crate::macros::cfg_select; diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index b58d3b7f1f539..d69a5aad4c26b 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -1771,7 +1771,7 @@ pub(crate) mod builtin { /// /// See [the reference] for more info. /// - /// [the reference]: ../reference/attributes/derive.html + /// [the reference]: ../../../reference/attributes/derive.html #[stable(feature = "rust1", since = "1.0.0")] #[rustc_builtin_macro] pub macro derive($item:item) { diff --git a/library/core/src/prelude/v1.rs b/library/core/src/prelude/v1.rs index 6122ab12ec351..f2eb047d342bc 100644 --- a/library/core/src/prelude/v1.rs +++ b/library/core/src/prelude/v1.rs @@ -120,13 +120,9 @@ pub use crate::trace_macros; // (no public module for them to be re-exported from). #[stable(feature = "builtin_macro_prelude", since = "1.38.0")] pub use crate::macros::builtin::{ - alloc_error_handler, bench, global_allocator, test, test_case, + alloc_error_handler, bench, derive, global_allocator, test, test_case, }; -#[stable(feature = "builtin_macro_prelude", since = "1.38.0")] -#[doc(no_inline)] -pub use crate::macros::builtin::derive; - #[unstable(feature = "derive_const", issue = "118304")] pub use crate::macros::builtin::derive_const; diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index e1061af1e7d6d..80ea577acd117 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -750,8 +750,6 @@ pub use core::cfg_select; reason = "`concat_bytes` is not stable enough for use and is subject to change" )] pub use core::concat_bytes; -#[unstable(feature = "derive_macro_global_path", issue = "154645")] -pub use core::derive; #[stable(feature = "matches_macro", since = "1.42.0")] #[allow(deprecated, deprecated_in_future)] pub use core::matches; diff --git a/library/std/src/prelude/v1.rs b/library/std/src/prelude/v1.rs index aeefec8b9e084..ee57e031c959c 100644 --- a/library/std/src/prelude/v1.rs +++ b/library/std/src/prelude/v1.rs @@ -115,13 +115,9 @@ pub use core::prelude::v1::trace_macros; // (no public module for them to be re-exported from). #[stable(feature = "builtin_macro_prelude", since = "1.38.0")] pub use core::prelude::v1::{ - alloc_error_handler, bench, global_allocator, test, test_case, + alloc_error_handler, bench, derive, global_allocator, test, test_case, }; -#[stable(feature = "builtin_macro_prelude", since = "1.38.0")] -#[doc(no_inline)] -pub use core::prelude::v1::derive; - #[unstable(feature = "derive_const", issue = "118304")] pub use core::prelude::v1::derive_const; diff --git a/tests/ui/imports/global-derive-path.rs b/tests/ui/imports/global-derive-path.rs deleted file mode 100644 index 5f0a6bb86bf30..0000000000000 --- a/tests/ui/imports/global-derive-path.rs +++ /dev/null @@ -1,10 +0,0 @@ -//@ edition: 2024 -//@ check-pass -#![crate_type = "lib"] -#![feature(derive_macro_global_path)] - -#[::core::derive(Clone)] -struct Y; - -#[::std::derive(Clone)] -struct X; From c9e24292ef70afae99d8c69c41cbea4a2fda0565 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 25 Jul 2026 13:28:49 +1000 Subject: [PATCH 37/63] Remove obsolete option `build.compiletest-use-stage0-libtest` --- src/bootstrap/src/core/config/config.rs | 2 -- src/bootstrap/src/core/config/toml/build.rs | 3 --- src/bootstrap/src/utils/change_tracker.rs | 5 +++++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 74be2409570f8..d516c89fba03c 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -532,8 +532,6 @@ impl Config { optimized_compiler_builtins: build_optimized_compiler_builtins, jobs: build_jobs, compiletest_diff_tool: build_compiletest_diff_tool, - // No longer has any effect; kept (for now) to avoid breaking people's configs. - compiletest_use_stage0_libtest: _, tidy_extra_checks: build_tidy_extra_checks, ccache: build_ccache, exclude: build_exclude, diff --git a/src/bootstrap/src/core/config/toml/build.rs b/src/bootstrap/src/core/config/toml/build.rs index 877dde676c605..d0fdbbed4e82d 100644 --- a/src/bootstrap/src/core/config/toml/build.rs +++ b/src/bootstrap/src/core/config/toml/build.rs @@ -72,9 +72,6 @@ define_config! { jobs: Option = "jobs", compiletest_diff_tool: Option = "compiletest-diff-tool", compiletest_allow_stage0: Option = "compiletest-allow-stage0", - /// No longer has any effect; kept (for now) to avoid breaking people's configs. - /// FIXME(#146929): Remove this in 2026. - compiletest_use_stage0_libtest: Option = "compiletest-use-stage0-libtest", tidy_extra_checks: Option = "tidy-extra-checks", ccache: Option = "ccache", exclude: Option> = "exclude", diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index 4252f2683b3d9..f5ab16c2e0a9a 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -651,4 +651,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Info, summary: "A new `build.sde` configuration option has been added to support intrinsic-test.", }, + ChangeInfo { + change_id: 159878, + severity: ChangeSeverity::Warning, + summary: "Obsolete option `build.compiletest-use-stage0-libtest` has no effect and has been removed.", + }, ]; From 36ef11a72c9fb1a5de60b0c8880c025f87111ddd Mon Sep 17 00:00:00 2001 From: Cole Kauder-McMurrich Date: Sat, 25 Jul 2026 00:50:42 -0400 Subject: [PATCH 38/63] Update expect messages in library/alloc/boxed.rs and library/alloc/string.rs to follow the style guide --- library/alloc/src/boxed.rs | 2 +- library/alloc/src/string.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index 58996703023ce..5190baaab8a26 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -1368,7 +1368,7 @@ impl Box { /// /// unsafe { /// let non_null = NonNull::new(alloc(Layout::new::()).cast::()) - /// .expect("allocation failed"); + /// .expect("alloc should have successfully allocated memory"); /// // In general .write is required to avoid attempting to destruct /// // the (uninitialized) previous contents of `non_null`. /// non_null.write(5); diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 6c19ba816050d..cc321660e6ea4 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -1314,7 +1314,7 @@ impl String { /// /// Ok(output) /// } - /// # process_data("rust").expect("why is the test harness OOMing on 4 bytes?"); + /// # process_data("rust").expect("reserving capacity for 12 bytes should never fail"); /// ``` #[stable(feature = "try_reserve", since = "1.57.0")] pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { @@ -1355,7 +1355,7 @@ impl String { /// /// Ok(output) /// } - /// # process_data("rust").expect("why is the test harness OOMing on 4 bytes?"); + /// # process_data("rust").expect("reserving capacity for 12 bytes should never fail"); /// ``` #[stable(feature = "try_reserve", since = "1.57.0")] pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> { From 05bb184cd682e2ffcedf904c7dd16994dcc39e0b Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 11:41:29 +0200 Subject: [PATCH 39/63] sembr src/variance.md --- src/doc/rustc-dev-guide/src/variance.md | 189 ++++++++++++------------ 1 file changed, 95 insertions(+), 94 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/variance.md b/src/doc/rustc-dev-guide/src/variance.md index 7aa0140715517..a8a72b44e87ad 100644 --- a/src/doc/rustc-dev-guide/src/variance.md +++ b/src/doc/rustc-dev-guide/src/variance.md @@ -4,30 +4,32 @@ For a more general background on variance, see the [background] appendix. [background]: ./appendix/background.html -During type checking we must infer the variance of type and lifetime -parameters. The algorithm is taken from Section 4 of the paper ["Taming the +During type checking we must infer the variance of type and lifetime parameters. +The algorithm is taken from Section 4 of the paper ["Taming the Wildcards: Combining Definition- and Use-Site Variance"][pldi11] published in PLDI'11 and written by Altidor et al., and hereafter referred to as The Paper. [pldi11]: https://people.cs.umass.edu/~yannis/variance-extended2011.pdf -This inference is explicitly designed *not* to consider the uses of -types within code. To determine the variance of type parameters +This inference is explicitly designed *not* to consider the uses of types within code. +To determine the variance of type parameters defined on type `X`, we only consider the definition of the type `X` and the definitions of any types it references. -We only infer variance for type parameters found on *data types* -like structs and enums. In these cases, there is a fairly straightforward -explanation for what variance means. The variance of the type -or lifetime parameters defines whether `T` is a subtype of `T` -(resp. `T<'a>` and `T<'b>`) based on the relationship of `A` and `B` -(resp. `'a` and `'b`). +We only infer variance for type parameters found on *data types* like structs and enums. +In these cases, there is a fairly straightforward explanation for what variance means. +The variance of the type or lifetime parameters defines whether `T` is a subtype of `T` +(resp. +`T<'a>` and `T<'b>`) based on the relationship of `A` and `B` (resp. +`'a` and `'b`). We do not infer variance for type parameters found on traits, functions, -or impls. Variance on trait parameters can indeed make sense +or impls. +Variance on trait parameters can indeed make sense (and we used to compute it) but it is actually rather subtle in -meaning and not that useful in practice, so we removed it. See the -[addendum] for some details. Variances on function/impl parameters, on the +meaning and not that useful in practice, so we removed it. +See the [addendum] for some details. +Variances on function/impl parameters, on the other hand, doesn't make sense because these parameters are instantiated and then forgotten, they don't persist in types or compiled byproducts. @@ -44,13 +46,12 @@ then forgotten, they don't persist in types or compiled byproducts. ## The algorithm -The basic idea is quite straightforward. We iterate over the types -defined and, for each use of a type parameter `X`, accumulate a -constraint indicating that the variance of `X` must be valid for the -variance of that use site. We then iteratively refine the variance of -`X` until all constraints are met. There is *always* a solution, because at -the limit we can declare all type parameters to be invariant and all -constraints will be satisfied. +The basic idea is quite straightforward. +We iterate over the types defined and, for each use of a type parameter `X`, accumulate a +constraint indicating that the variance of `X` must be valid for the variance of that use site. +We then iteratively refine the variance of `X` until all constraints are met. +There is *always* a solution, because at +the limit we can declare all type parameters to be invariant and all constraints will be satisfied. As a simple example, consider: @@ -71,8 +72,8 @@ Here, we will generate the constraints: These indicate that (1) the variance of A must be at most covariant; (2) the variance of B must be at most contravariant; and (3, 4) the -variance of C must be at most covariant *and* contravariant. All of these -results are based on a variance lattice defined as follows: +variance of C must be at most covariant *and* contravariant. +All of these results are based on a variance lattice defined as follows: ```text * Top (bivariant) @@ -80,14 +81,13 @@ results are based on a variance lattice defined as follows: o Bottom (invariant) ``` -Based on this lattice, the solution `V(A)=+`, `V(B)=-`, `V(C)=o` is the -optimal solution. Note that there is always a naive solution which -just declares all variables to be invariant. +Based on this lattice, the solution `V(A)=+`, `V(B)=-`, `V(C)=o` is the optimal solution. +Note that there is always a naive solution which just declares all variables to be invariant. -You may be wondering why fixed-point iteration is required. The reason -is that the variance of a use site may itself be a function of the -variance of other type parameters. In full generality, our constraints -take the form: +You may be wondering why fixed-point iteration is required. +The reason is that the variance of a use site may itself be a function of the +variance of other type parameters. +In full generality, our constraints take the form: ```text V(X) <= Term @@ -95,8 +95,8 @@ Term := + | - | * | o | V(X) | Term x Term ``` Here the notation `V(X)` indicates the variance of a type/region -parameter `X` with respect to its defining class. `Term x Term` -represents the "variance transform" as defined in the paper: +parameter `X` with respect to its defining class. +`Term x Term` represents the "variance transform" as defined in the paper: > If the variance of a type variable `X` in type expression `E` is `V2` and the definition-site variance of the corresponding type parameter @@ -112,19 +112,21 @@ struct Foo { ... } ``` you might wonder whether the variance of `T` with respect to `Bar` affects the -variance `T` with respect to `Foo`. I claim no. The reason: assume that `T` is -invariant with respect to `Bar` but covariant with respect to `Foo`. And then -we have a `Foo` that is upcast to `Foo`, where `X <: Y`. However, while -`X : Bar`, `Y : Bar` does not hold. In that case, the upcast will be illegal, +variance `T` with respect to `Foo`. +I claim no. + The reason: assume that `T` is invariant with respect to `Bar` but covariant with respect to `Foo`. +And then we have a `Foo` that is upcast to `Foo`, where `X <: Y`. +However, while `X : Bar`, `Y : Bar` does not hold. + In that case, the upcast will be illegal, but not because of a variance failure, but rather because the target type -`Foo` is itself just not well-formed. Basically we get to assume -well-formedness of all types involved before considering variance. +`Foo` is itself just not well-formed. +Basically we get to assume well-formedness of all types involved before considering variance. ### Dependency graph management Because variance is a whole-crate inference, its dependency graph -can become quite muddled if we are not careful. To resolve this, we refactor -into two queries: +can become quite muddled if we are not careful. +To resolve this, we refactor into two queries: - `crate_variances` computes the variance for all items in the current crate. - `variances_of` accesses the variance for an individual reading; it @@ -133,7 +135,8 @@ into two queries: If you limit yourself to reading `variances_of`, your code will only depend then on the inference of that particular item. -Ultimately, this setup relies on the [red-green algorithm][rga]. In particular, +Ultimately, this setup relies on the [red-green algorithm][rga]. +In particular, every variance query effectively depends on all type definitions in the entire crate (through `crate_variances`), but since most changes will not result in a change to the actual results from variance inference, the `variances_of` query @@ -145,19 +148,18 @@ will wind up being considered green after it is re-evaluated. ## Addendum: Variance on traits -As mentioned above, we used to permit variance on traits. This was -computed based on the appearance of trait type parameters in +As mentioned above, we used to permit variance on traits. +This was computed based on the appearance of trait type parameters in method signatures and was used to represent the compatibility of -vtables in trait objects (and also "virtual" vtables or dictionary -in trait bounds). One complication was that variance for +vtables in trait objects (and also "virtual" vtables or dictionary in trait bounds). +One complication was that variance for associated types is less obvious, since they can be projected out and put to myriad uses, so it's not clear when it is safe to allow -`X::Bar` to vary (or indeed just what that means). Moreover (as -covered below) all inputs on any trait with an associated type had -to be invariant, limiting the applicability. Finally, the -annotations (`MarkerTrait`, `PhantomFn`) needed to ensure that all -trait type parameters had a variance were confusing and annoying -for little benefit. +`X::Bar` to vary (or indeed just what that means). +Moreover (as covered below) all inputs on any trait with an associated type had +to be invariant, limiting the applicability. +Finally, the annotations (`MarkerTrait`, `PhantomFn`) needed to ensure that all +trait type parameters had a variance were confusing and annoying for little benefit. Just for historical reference, I am going to preserve some text indicating how one could interpret variance and trait matching. @@ -166,13 +168,12 @@ one could interpret variance and trait matching. Just as with structs and enums, we can decide the subtyping relationship between two object types `&Trait` and `&Trait` -based on the relationship of `A` and `B`. Note that for object -types we ignore the `Self` type parameter – it is unknown, and +based on the relationship of `A` and `B`. +Note that for object types we ignore the `Self` type parameter – it is unknown, and the nature of dynamic dispatch ensures that we will always call a -function that is expected the appropriate `Self` type. However, we -must be careful with the other type parameters, or else we could -end up calling a function that is expecting one type but provided -another. +function that is expected the appropriate `Self` type. +However, we must be careful with the other type parameters, or else we could +end up calling a function that is expecting one type but provided another. To see what I mean, consider a trait like so: @@ -184,29 +185,29 @@ trait ConvertTo { Intuitively, If we had one object `O=&ConvertTo` and another `S=&ConvertTo`, then `S <: O` because `String <: Object` -(presuming Java-like "string" and "object" types, my go to examples -for subtyping). The actual algorithm would be to compare the +(presuming Java-like "string" and "object" types, my go to examples for subtyping). +The actual algorithm would be to compare the (explicit) type parameters pairwise respecting their variance: here, the type parameter A is covariant (it appears only in a return position), and hence we require that `String <: Object`. You'll note though that we did not consider the binding for the -(implicit) `Self` type parameter: in fact, it is unknown, so that's -good. The reason we can ignore that parameter is precisely because we +(implicit) `Self` type parameter: in fact, it is unknown, so that's good. +The reason we can ignore that parameter is precisely because we don't need to know its value until a call occurs, and at that time (as you said) the dynamic nature of virtual dispatch means the code we run will be correct for whatever value `Self` happens to be bound to for -the particular object whose method we called. `Self` is thus different -from `A`, because the caller requires that `A` be known in order to -know the return type of the method `convertTo()`. (As an aside, we -have rules preventing methods where `Self` appears outside of the +the particular object whose method we called. +`Self` is thus different from `A`, because the caller requires that `A` be known in order to +know the return type of the method `convertTo()`. +(As an aside, we have rules preventing methods where `Self` appears outside of the receiver position from being called via an object.) ### Trait variance and vtable resolution -But traits aren't only used with objects. They're also used when -deciding whether a given impl satisfies a given trait bound. To set the -scene here, imagine I had a function: +But traits aren't only used with objects. +They're also used when deciding whether a given impl satisfies a given trait bound. +To set the scene here, imagine I had a function: ```rust,ignore fn convertAll>(v: &[T]) { ... } @@ -218,8 +219,8 @@ Now imagine that I have an implementation of `ConvertTo` for `Object`: impl ConvertTo for Object { ... } ``` -And I want to call `convertAll` on an array of strings. Suppose -further that for whatever reason I specifically supply the value of +And I want to call `convertAll` on an array of strings. +Suppose further that for whatever reason I specifically supply the value of `String` for the type parameter `T`: ```rust,ignore @@ -227,26 +228,25 @@ let mut vector = vec!["string", ...]; convertAll::(vector); ``` -Is this legal? To put another way, can we apply the `impl` for -`Object` to the type `String`? The answer is yes, but to see why -we have to expand out what will happen: +Is this legal? +To put another way, can we apply the `impl` for `Object` to the type `String`? +The answer is yes, but to see why we have to expand out what will happen: - `convertAll` will create a pointer to one of the entries in the vector, which will have type `&String` -- It will then call the impl of `convertTo()` that is intended - for use with objects. This has the type `fn(self: &Object) -> i32`. +- It will then call the impl of `convertTo()` that is intended for use with objects. + This has the type `fn(self: &Object) -> i32`. - It is OK to provide a value for `self` of type `&String` because - `&String <: &Object`. + It is OK to provide a value for `self` of type `&String` because `&String <: &Object`. OK, so intuitively we want this to be legal, so let's bring this back -to variance and see whether we are computing the correct result. We -must first figure out how to phrase the question "is an impl for +to variance and see whether we are computing the correct result. +We must first figure out how to phrase the question "is an impl for `Object,i32` usable where an impl for `String,i32` is expected?" -Maybe it's helpful to think of a dictionary-passing implementation of -type classes. In that case, `convertAll()` takes an implicit parameter -representing the impl. In short, we *have* an impl of type: +Maybe it's helpful to think of a dictionary-passing implementation of type classes. +In that case, `convertAll()` takes an implicit parameter representing the impl. +In short, we *have* an impl of type: ```text V_O = ConvertTo for Object @@ -259,9 +259,10 @@ V_S = ConvertTo for String ``` As with any argument, this is legal if the type of the value given -(`V_O`) is a subtype of the type expected (`V_S`). So is `V_O <: V_S`? -The answer will depend on the variance of the various parameters. In -this case, because the `Self` parameter is contravariant and `A` is +(`V_O`) is a subtype of the type expected (`V_S`). +So is `V_O <: V_S`? +The answer will depend on the variance of the various parameters. +In this case, because the `Self` parameter is contravariant and `A` is covariant, it means that: ```text @@ -275,27 +276,26 @@ These conditions are satisfied and so we are happy. ### Variance and associated types Traits with associated types – or at minimum projection -expressions – must be invariant with respect to all of their -inputs. To see why this makes sense, consider what subtyping for a -trait reference means: +expressions – must be invariant with respect to all of their inputs. +To see why this makes sense, consider what subtyping for a trait reference means: ```text <: ``` -means that if I know that `T as Trait`, I also know that `U as -Trait`. Moreover, if you think of it as dictionary passing style, +means that if I know that `T as Trait`, I also know that `U as Trait`. +Moreover, if you think of it as dictionary passing style, it means that a dictionary for `` is safe to use where a dictionary for `` is expected. The problem is that when you can project types out from ``, the relationship to types projected out of `` -is completely unknown unless `T==U` (see #21726 for more -details). Making `Trait` invariant ensures that this is true. +is completely unknown unless `T==U` (see #21726 for more details). +Making `Trait` invariant ensures that this is true. Another related reason is that if we didn't make traits with -associated types invariant, then projection is no longer a -function with a single result. Consider: +associated types invariant, then projection is no longer a function with a single result. +Consider: ```rust,ignore trait Identity { type Out; fn foo(&self); } @@ -313,4 +313,5 @@ if 'static : 'a -- Subtyping rules for relations This change otoh means that `<'static () as Identity>::Out` is always `&'static ()` (which might then be upcast to `'a ()`, -separately). This was helpful in solving #21750. +separately). +This was helpful in solving #21750. From 5d19c9290758240c9bbcfee9eebfb47f5360792b Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Sat, 25 Jul 2026 09:42:21 +0000 Subject: [PATCH 40/63] Prepare for merging from rust-lang/rust This updates the rust-version file to da86f4d0726be475afbbffe40cb2f65741c51ad3. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index 25a5238f538ee..1a74dff9d23d8 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -390279b302ca98ae270f434100ae3730531d1246 +da86f4d0726be475afbbffe40cb2f65741c51ad3 From acb94075972be23872ca1c010029bf8f2c399a10 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sat, 25 Jul 2026 12:01:39 +0200 Subject: [PATCH 41/63] Split multiline derives into std/rustc macros --- compiler/rustc_data_structures/src/svh.rs | 13 +----- .../rustc_hir/src/attrs/data_structures.rs | 15 +------ compiler/rustc_lint_defs/src/lib.rs | 5 +-- .../rustc_middle/src/dep_graph/dep_node.rs | 5 +-- compiler/rustc_span/src/lib.rs | 6 +-- compiler/rustc_target/src/asm/amdgpu.rs | 45 +++---------------- 6 files changed, 17 insertions(+), 72 deletions(-) diff --git a/compiler/rustc_data_structures/src/svh.rs b/compiler/rustc_data_structures/src/svh.rs index 2c4e00c824d80..67594f6dae79d 100644 --- a/compiler/rustc_data_structures/src/svh.rs +++ b/compiler/rustc_data_structures/src/svh.rs @@ -11,17 +11,8 @@ use rustc_macros::{Decodable_NoContext, Encodable_NoContext, StableHash}; use crate::fingerprint::Fingerprint; -#[derive( - Copy, - Clone, - PartialEq, - Eq, - Debug, - Encodable_NoContext, - Decodable_NoContext, - Hash, - StableHash -)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +#[derive(Encodable_NoContext, Decodable_NoContext, StableHash)] pub struct Svh { hash: Fingerprint, } diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index db492475a3aa4..efe3657f21ea7 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -370,19 +370,8 @@ pub enum PeImportNameType { Undecorated, } -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Encodable, - Decodable, - PrintAttribute -)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Encodable, Decodable, PrintAttribute)] #[derive(StableHash)] pub enum NativeLibKind { /// Static library (e.g. `libfoo.a` on Linux or `foo.lib` on Windows/MSVC) diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 6949090781cf6..767fa647612af 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -149,9 +149,8 @@ impl From for LintExpectationId { /// Setting for how to handle a lint. /// /// See: -#[derive( - Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Encodable, Decodable, StableHash -)] +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] +#[derive(Encodable, Decodable, StableHash)] pub enum Level { /// The `allow` level will not issue any message. Allow, diff --git a/compiler/rustc_middle/src/dep_graph/dep_node.rs b/compiler/rustc_middle/src/dep_graph/dep_node.rs index 391bd41be828c..f460fd42cb31c 100644 --- a/compiler/rustc_middle/src/dep_graph/dep_node.rs +++ b/compiler/rustc_middle/src/dep_graph/dep_node.rs @@ -217,9 +217,8 @@ pub struct DepKindVTable<'tcx> { /// some independent path or string that persists between runs without /// the need to be mapped or unmapped. (This ensures we can serialize /// them even in the absence of a tcx.) -#[derive( - Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Encodable, Decodable, StableHash -)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Encodable, Decodable, StableHash)] pub struct WorkProductId { hash: Fingerprint, } diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 318d0b60e240d..b8cf472309ba5 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -2126,10 +2126,8 @@ impl fmt::Debug for SourceFile { /// /// When `SourceFile`s are exported in crate metadata, the `StableSourceFileId` /// is updated to incorporate the `StableCrateId` of the exporting crate. -#[derive( - Debug, Clone, Copy, Hash, PartialEq, Eq, StableHash, Encodable, Decodable, Default, PartialOrd, - Ord -)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Default, Ord)] +#[derive(StableHash, Encodable, Decodable)] pub struct StableSourceFileId(Hash128); impl StableSourceFileId { diff --git a/compiler/rustc_target/src/asm/amdgpu.rs b/compiler/rustc_target/src/asm/amdgpu.rs index cc6f612cdb8b5..0f24ae5dea225 100644 --- a/compiler/rustc_target/src/asm/amdgpu.rs +++ b/compiler/rustc_target/src/asm/amdgpu.rs @@ -1,5 +1,6 @@ use std::fmt; +use rustc_macros::{Decodable, Encodable, StableHash}; use rustc_span::Symbol; use super::{InlineAsmArch, InlineAsmType, ModifierInfo}; @@ -9,19 +10,8 @@ use super::{InlineAsmArch, InlineAsmType, ModifierInfo}; /// Amdgpu register classes /// /// The number is the size of the register class in bits. -#[derive( - Copy, - Clone, - rustc_macros::Encodable, - rustc_macros::Decodable, - Debug, - Eq, - PartialEq, - PartialOrd, - Hash, - rustc_macros::StableHash -)] -#[allow(non_camel_case_types)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[derive(StableHash, Encodable, Decodable)] pub enum AmdgpuInlineAsmRegClass { Sgpr(u16), Vgpr(u16), @@ -267,18 +257,8 @@ impl AmdgpuInlineAsmRegClass { /// Start index of a register. /// /// Together with the register size this gives the range occupied by a register. -#[derive( - Copy, - Clone, - rustc_macros::Encodable, - rustc_macros::Decodable, - Debug, - Eq, - PartialEq, - PartialOrd, - Hash, - rustc_macros::StableHash -)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[derive(Encodable, Decodable, StableHash)] enum AmdgpuRegStart { /// Low 16-bit of the register at this index Low(u16), @@ -288,19 +268,8 @@ enum AmdgpuRegStart { Full(u16), } -#[derive( - Copy, - Clone, - rustc_macros::Encodable, - rustc_macros::Decodable, - Debug, - Eq, - PartialEq, - PartialOrd, - Hash, - rustc_macros::StableHash -)] -#[allow(non_camel_case_types)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[derive(Encodable, Decodable, StableHash)] pub struct AmdgpuInlineAsmReg { class: AmdgpuInlineAsmRegClass, range: AmdgpuRegStart, From d57ee2221b032116c9edac9666d720e3760dc9ef Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:03:12 +0200 Subject: [PATCH 42/63] improve variance.md --- src/doc/rustc-dev-guide/src/variance.md | 35 +++++++++++-------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/variance.md b/src/doc/rustc-dev-guide/src/variance.md index a8a72b44e87ad..96fde1d87cca5 100644 --- a/src/doc/rustc-dev-guide/src/variance.md +++ b/src/doc/rustc-dev-guide/src/variance.md @@ -4,7 +4,7 @@ For a more general background on variance, see the [background] appendix. [background]: ./appendix/background.html -During type checking we must infer the variance of type and lifetime parameters. +During type checking, we must infer the variance of type and lifetime parameters. The algorithm is taken from Section 4 of the paper ["Taming the Wildcards: Combining Definition- and Use-Site Variance"][pldi11] published in PLDI'11 and written by Altidor et al., and hereafter referred to as The Paper. @@ -12,26 +12,23 @@ PLDI'11 and written by Altidor et al., and hereafter referred to as The Paper. [pldi11]: https://people.cs.umass.edu/~yannis/variance-extended2011.pdf This inference is explicitly designed *not* to consider the uses of types within code. -To determine the variance of type parameters -defined on type `X`, we only consider the definition of the type `X` -and the definitions of any types it references. +To determine the variance of type parameters defined on type `X`, +we only consider the definition of the type `X` and the definitions of any types it references. We only infer variance for type parameters found on *data types* like structs and enums. In these cases, there is a fairly straightforward explanation for what variance means. The variance of the type or lifetime parameters defines whether `T` is a subtype of `T` -(resp. -`T<'a>` and `T<'b>`) based on the relationship of `A` and `B` (resp. -`'a` and `'b`). +(`T<'a>` and `T<'b>` respectively) +based on the relationship of `A` and `B` (`'a` and `'b` respectively). -We do not infer variance for type parameters found on traits, functions, -or impls. +We do not infer variance for type parameters found on traits, functions, or impls. Variance on trait parameters can indeed make sense (and we used to compute it) but it is actually rather subtle in meaning and not that useful in practice, so we removed it. See the [addendum] for some details. -Variances on function/impl parameters, on the -other hand, doesn't make sense because these parameters are instantiated and -then forgotten, they don't persist in types or compiled byproducts. +Variances on function/impl parameters, on the other hand, +doesn't make sense because these parameters are instantiated and then forgotten; +they don't persist in types or compiled byproducts. [addendum]: #addendum @@ -111,16 +108,16 @@ If I have a struct or enum with where clauses: struct Foo { ... } ``` -you might wonder whether the variance of `T` with respect to `Bar` affects the +You might wonder whether the variance of `T` with respect to `Bar` affects the variance `T` with respect to `Foo`. I claim no. - The reason: assume that `T` is invariant with respect to `Bar` but covariant with respect to `Foo`. +The reason: assume that `T` is invariant with respect to `Bar` but covariant with respect to `Foo`. And then we have a `Foo` that is upcast to `Foo`, where `X <: Y`. However, while `X : Bar`, `Y : Bar` does not hold. - In that case, the upcast will be illegal, +In that case, the upcast will be illegal, but not because of a variance failure, but rather because the target type `Foo` is itself just not well-formed. -Basically we get to assume well-formedness of all types involved before considering variance. +Basically, we get to assume well-formedness of all types involved before considering variance. ### Dependency graph management @@ -230,7 +227,7 @@ convertAll::(vector); Is this legal? To put another way, can we apply the `impl` for `Object` to the type `String`? -The answer is yes, but to see why we have to expand out what will happen: +The answer is yes, but to see why, we have to expand out what will happen: - `convertAll` will create a pointer to one of the entries in the vector, which will have type `&String` @@ -252,7 +249,7 @@ In short, we *have* an impl of type: V_O = ConvertTo for Object ``` -and the function prototype expects an impl of type: +And the function prototype expects an impl of type: ```text V_S = ConvertTo for String @@ -283,7 +280,7 @@ To see why this makes sense, consider what subtyping for a trait reference means <: ``` -means that if I know that `T as Trait`, I also know that `U as Trait`. +It means that if I know that `T as Trait`, I also know that `U as Trait`. Moreover, if you think of it as dictionary passing style, it means that a dictionary for `` is safe to use where a dictionary for `` is expected. From e481f4875f2cc5402ee5f519ee11aeb9d3d3a613 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 25 Jul 2026 12:13:42 +0200 Subject: [PATCH 43/63] Correctly handle path comparison on windows --- tests/run-make/rustdoc-show-coverage/rmake.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run-make/rustdoc-show-coverage/rmake.rs b/tests/run-make/rustdoc-show-coverage/rmake.rs index cc4956f2c58c2..ca8586fb2796f 100644 --- a/tests/run-make/rustdoc-show-coverage/rmake.rs +++ b/tests/run-make/rustdoc-show-coverage/rmake.rs @@ -3,6 +3,7 @@ //@ needs-target-std +use run_make_support::assertion_helpers::assert_contains_regex; use run_make_support::rfs::{read_to_string, remove_file}; use run_make_support::{path, rustdoc}; @@ -36,8 +37,7 @@ fn check_generate_file(ext: &str, extra_args: &[&str], file_check: &str) { let file = format!("doc/foo.{ext}"); assert!(path(&file).exists()); - let expected = format!("Generated output into {file:?}\n"); - assert_eq!(out, expected, "Expected {expected:?}, got {out:?}"); + assert_contains_regex(out, format!("Generated output into \"doc[/\\\\]foo.{ext}\"\n")); let content = read_to_string(&file); assert!(content.starts_with(file_check), "{content:?} doesn't start with {file_check:?}"); From 41962835685e134a52b6e43df81a173fb2adf39b Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sat, 25 Jul 2026 17:46:44 +0800 Subject: [PATCH 44/63] Add warning for breakage hazard for introducing new builtin attrs --- src/doc/rustc-dev-guide/src/attributes.md | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/doc/rustc-dev-guide/src/attributes.md b/src/doc/rustc-dev-guide/src/attributes.md index 1056a257c2ee8..8dce9b8c17b18 100644 --- a/src/doc/rustc-dev-guide/src/attributes.md +++ b/src/doc/rustc-dev-guide/src/attributes.md @@ -25,6 +25,34 @@ For more information on these attributes, see the chapter about [attribute parsi [attr-parsing-chapter]: ./hir/attribute-parsing.md +### Note on adding new builtin attributes + +
+ +**Warning: Name resolution ambiguity potential when adding new builtin attributes** + +Please note that adding **new builtin attributes** (whose name is not reserved, i.e. a new builtin +attribute whose name does not start with `rustc`), even if *unstable*-gated, can introduce breakage +from name resolution ambiguity in stable code if (1) the stable code has a macro of the same name +which gets re-exported, or (2) or a proc-macro derive helper attribute of the same name. + +Typically, the builtin attributes probably has to start out as `#[rustc_foo]` instead of `#[foo]` to +avoid colliding with user-defined macros and proc-macro helper attributes. Then, prior to +stabilization, a rename to `#[foo]` should be done separately with a crater run to assess fallout, +with a deliberate breakage FCP proposal for T-lang to consider. + +Remember also that crater is *not* exhaustive and does not contain all existing stable code. + +See: +- [Built-in attributes are treated differently vs prelude attributes, unstable built-in attributes + can name-collide with stable macro, and built-in attributes can break back-compat + #134963](https://github.com/rust-lang/rust/issues/134963) and backlinks within this issue, + including design discussions on how to fix this kind of breakage hazard. +- [Broken build after updating: coverage is ambiguous; ambiguous because of a name conflict with a + builtin attribute #121157](https://github.com/rust-lang/rust/issues/121157). +- [Regression: align is ambiguous #143834](https://github.com/rust-lang/rust/issues/143834). +
+ ## 'Non-builtin'/'active' attributes These attributes are defined by a crate - either the standard library, or a proc-macro crate. From f36f2b96c6526b53eb3db56af6e8cfde596ff966 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:16:13 +0200 Subject: [PATCH 45/63] sembr src/rustdoc-internals/rustdoc-gui-test-suite.md --- .../src/rustdoc-internals/rustdoc-gui-test-suite.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md index d18021bf278d9..e4909110d0aa6 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md @@ -5,7 +5,8 @@ This page is about the test suite named `rustdoc-gui` used to test the "GUI" of `rustdoc` (i.e., the HTML/JS/CSS as rendered in a browser). For other rustdoc-specific test suites, see [Rustdoc test suites]. -These use a NodeJS-based tool called [`browser-UI-test`] that uses [puppeteer] to run tests in a headless browser and check rendering and interactivity. For information on how to write this form of test, see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] +These use a NodeJS-based tool called [`browser-UI-test`] that uses [puppeteer] to run tests in a headless browser and check rendering and interactivity. +For information on how to write this form of test, see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] [Rustdoc test suites]: ../tests/compiletest.md#rustdoc-test-suites [`browser-UI-test`]: https://github.com/GuillaumeGomez/browser-UI-test/ From 0d9c6a4e5a06df464a3187d613ac97e9d5d69406 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:17:11 +0200 Subject: [PATCH 46/63] overlong --- .../src/rustdoc-internals/rustdoc-gui-test-suite.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md index e4909110d0aa6..bce0fe4b0dc54 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md @@ -6,7 +6,8 @@ This page is about the test suite named `rustdoc-gui` used to test the "GUI" of For other rustdoc-specific test suites, see [Rustdoc test suites]. These use a NodeJS-based tool called [`browser-UI-test`] that uses [puppeteer] to run tests in a headless browser and check rendering and interactivity. -For information on how to write this form of test, see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] +For information on how to write this form of test, +see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] [Rustdoc test suites]: ../tests/compiletest.md#rustdoc-test-suites [`browser-UI-test`]: https://github.com/GuillaumeGomez/browser-UI-test/ From c5659980e31f84cd0ea029fc33ebe52225b4bbbb Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:17:30 +0200 Subject: [PATCH 47/63] punctuation --- .../src/rustdoc-internals/rustdoc-gui-test-suite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md index bce0fe4b0dc54..951a541548b23 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-gui-test-suite.md @@ -7,7 +7,7 @@ For other rustdoc-specific test suites, see [Rustdoc test suites]. These use a NodeJS-based tool called [`browser-UI-test`] that uses [puppeteer] to run tests in a headless browser and check rendering and interactivity. For information on how to write this form of test, -see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script] +see [`tests/rustdoc-gui/README.md`][rustdoc-gui-readme] as well as [the description of the `.goml` format][goml-script]. [Rustdoc test suites]: ../tests/compiletest.md#rustdoc-test-suites [`browser-UI-test`]: https://github.com/GuillaumeGomez/browser-UI-test/ From 54c2643a9bb0946123d8a8efe21c493f503c548f Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 25 Jul 2026 12:18:02 +0200 Subject: [PATCH 48/63] sembr src/rustdoc-internals/search.md --- .../src/rustdoc-internals/search.md | 78 ++++++++----------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md index e88d2703e3a23..d2235e84073b9 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md @@ -1,16 +1,13 @@ # Rustdoc search -Rustdoc Search is two programs: `search_index.rs` -and `search.js`. The first generates a nasty JSON -file with a full list of items and function signatures +Rustdoc Search is two programs: `search_index.rs` and `search.js`. +The first generates a nasty JSON file with a full list of items and function signatures in the crates in the doc bundle, and the second reads -it, turns it into some in-memory structures, and -scans them linearly to search. +it, turns it into some in-memory structures, and scans them linearly to search. ## Search index format -`search.js` calls this Raw, because it turns it into -a more normal object tree after loading it. +`search.js` calls this Raw, because it turns it into a more normal object tree after loading it. For space savings, it's also written without newlines or spaces. ```json @@ -44,8 +41,7 @@ For space savings, it's also written without newlines or spaces. ] ``` -[`src/librustdoc/html/static/js/rustdoc.d.ts`] -defines an actual schema in a TypeScript `type`. +[`src/librustdoc/html/static/js/rustdoc.d.ts`] defines an actual schema in a TypeScript `type`. | Key | Name | Description | | --- | -------------------- | ------------ | @@ -75,10 +71,8 @@ It makes a lot of compromises: * The `rustdoc` compiler runs on one crate at a time, so each crate has an essentially separate search index. - It [merges] them by having each crate on one line - and looking at the first quoted string. -* Names in the search index are given - in their original case and with underscores. + It [merges] them by having each crate on one line and looking at the first quoted string. +* Names in the search index are given in their original case and with underscores. When the search index is loaded, `search.js` stores the original names for display, but also folds them to lowercase and strips underscores for search. @@ -156,10 +150,8 @@ for (i, entry) in search_index.iter().enumerate() { } ``` -This is valid because everything has a parent module -(even if it's just the crate itself), -and is easy to assemble because the rustdoc generator sorts by path -before serializing. +This is valid because everything has a parent module (even if it's just the crate itself), +and is easy to assemble because the rustdoc generator sorts by path before serializing. Doing this allows rustdoc to not only make the search index smaller, but reuse the same string representing the parent path across multiple in-memory items. @@ -233,21 +225,21 @@ work through a "sandwich workload" of three steps: Reducing the amount of data downloaded here will almost always increase latency, by delaying the decision of what to download behind other work and/or adding -data dependencies where something can't be downloaded without first downloading -something else. In this case, we can't start downloading descriptions until +data dependencies where something can't be downloaded without first downloading something else. +In this case, we can't start downloading descriptions until after the search is done, because that's what allows it to decide *which* descriptions to download (it needs to sort the results then truncate to 200). To do this, two columns are stored in the search index, building on both Roaring Bitmaps and on VLQ Hex. -* `e` is an index of **e**mpty descriptions. It's a [roaring bitmap] of - each item (the crate itself is item 0, the rest start at 1). +* `e` is an index of **e**mpty descriptions. + It's a [roaring bitmap] of each item (the crate itself is item 0, the rest start at 1). * `D` is a shard list, stored in [VLQ hex] as flat list of integers. Each integer gives you the number of descriptions in the shard. As the decoder walks the index, it checks if the description is empty. - if it's not, then it's in the "current" shard. When all items are - exhausted, it goes on to the next shard. + if it's not, then it's in the "current" shard. + When all items are exhausted, it goes on to the next shard. Inside each shard is a newline-delimited list of descriptions, wrapped in a JSONP-style function call. @@ -280,8 +272,7 @@ Because of zigzag encoding, `` ` `` is +0, `a` is -0 (which is not used), ## Searching by name -Searching by name works by looping through the search index -and running these functions on each: +Searching by name works by looping through the search index and running these functions on each: * [`editDistance`] is always used to determine a match (unless quotes are specified, which would use simple equality instead). @@ -295,20 +286,19 @@ and running these functions on each: If it returns anything other than -1, the result is added, even if `editDistance` exceeds its threshold, and the index is stored for ranking. -* [`checkPath`] is used if, and only if, a parent path is specified - in the query. For example, `vec` has no parent path, but `vec::vec` does. +* [`checkPath`] is used if, and only if, a parent path is specified in the query. + For example, `vec` has no parent path, but `vec::vec` does. Within checkPath, editDistance and indexOf are used, and the path query has its own heuristic threshold, too. If it's not within the threshold, the entry is rejected, even if the first two pass. - If it's within the threshold, the path distance is stored - for ranking. + If it's within the threshold, the path distance is stored for ranking. * [`checkType`] is used only if there's a type filter, - like the struct in `struct:vec`. If it fails, + like the struct in `struct:vec`. + If it fails, the entry is rejected. -If all four criteria pass -(plus the crate filter, which isn't technically part of the query), +If all four criteria pass (plus the crate filter, which isn't technically part of the query), the results are sorted by [`sortResults`]. [`editDistance`]: https://github.com/rust-lang/rust/blob/79b710c13968a1a48d94431d024d2b1677940866/src/librustdoc/html/static/js/search.js#L137 @@ -336,17 +326,18 @@ is going to match the same things `T, u32` matches (though rustdoc will detect this particular problem and warn about it). Then, when actually looping over each item, -the bloom filter will probably reject entries that don't have every -type mentioned in the query. +the bloom filter will probably reject entries that don't have every type mentioned in the query. For example, the bloom query allows a query of `i32 -> u32` to match a function with the type `i32, u32 -> bool`, but unification will reject it later. The unification filter ensures that: -* Bag semantics are respected. If you query says `i32, i32`, +* Bag semantics are respected. + If you query says `i32, i32`, then the function has to mention *two* i32s, not just one. -* Nesting semantics are respected. If your query says `vec