From c78387192fe6441df078bd00d18d1f8e35161770 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 9 Nov 2025 18:59:28 +1100 Subject: [PATCH 01/24] coverage: Hoist expansion tree creation out of span refinement This is an incremental step towards making the expansion tree central to coverage mapping creation, which will be needed for proper expansion region support. --- .../src/coverage/expansion.rs | 14 +++++++++++--- .../src/coverage/{spans => }/from_mir.rs | 0 .../src/coverage/mappings.rs | 5 ++++- .../rustc_mir_transform/src/coverage/mod.rs | 1 + .../rustc_mir_transform/src/coverage/spans.rs | 17 +++-------------- 5 files changed, 19 insertions(+), 18 deletions(-) rename compiler/rustc_mir_transform/src/coverage/{spans => }/from_mir.rs (100%) diff --git a/compiler/rustc_mir_transform/src/coverage/expansion.rs b/compiler/rustc_mir_transform/src/coverage/expansion.rs index 851bbaeed48eb..df27b1ccabe1f 100644 --- a/compiler/rustc_mir_transform/src/coverage/expansion.rs +++ b/compiler/rustc_mir_transform/src/coverage/expansion.rs @@ -1,7 +1,11 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry}; +use rustc_middle::mir; use rustc_middle::mir::coverage::BasicCoverageBlock; use rustc_span::{ExpnId, ExpnKind, Span}; +use crate::coverage::from_mir; +use crate::coverage::graph::CoverageGraph; + #[derive(Clone, Copy, Debug)] pub(crate) struct SpanWithBcb { pub(crate) span: Span, @@ -92,13 +96,17 @@ impl ExpnNode { } } -/// Given a collection of span/BCB pairs from potentially-different syntax contexts, +/// Extracts raw span/BCB pairs from potentially-different syntax contexts, and /// arranges them into an "expansion tree" based on their expansion call-sites. -pub(crate) fn build_expn_tree(spans: impl IntoIterator) -> ExpnTree { +pub(crate) fn build_expn_tree(mir_body: &mir::Body<'_>, graph: &CoverageGraph) -> ExpnTree { + let raw_spans = from_mir::extract_raw_spans_from_mir(mir_body, graph); + let mut nodes = FxIndexMap::default(); let new_node = |&expn_id: &ExpnId| ExpnNode::new(expn_id); - for span_with_bcb in spans { + for from_mir::RawSpanFromMir { raw_span, bcb } in raw_spans { + let span_with_bcb = SpanWithBcb { span: raw_span, bcb }; + // Create a node for this span's enclosing expansion, and add the span to it. let expn_id = span_with_bcb.span.ctxt().outer_expn(); let node = nodes.entry(expn_id).or_insert_with_key(new_node); diff --git a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs b/compiler/rustc_mir_transform/src/coverage/from_mir.rs similarity index 100% rename from compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs rename to compiler/rustc_mir_transform/src/coverage/from_mir.rs diff --git a/compiler/rustc_mir_transform/src/coverage/mappings.rs b/compiler/rustc_mir_transform/src/coverage/mappings.rs index 8dbe564f51749..f632cd02d8326 100644 --- a/compiler/rustc_mir_transform/src/coverage/mappings.rs +++ b/compiler/rustc_mir_transform/src/coverage/mappings.rs @@ -5,6 +5,7 @@ use rustc_middle::mir::coverage::{ use rustc_middle::mir::{self, BasicBlock, StatementKind}; use rustc_middle::ty::TyCtxt; +use crate::coverage::expansion; use crate::coverage::graph::CoverageGraph; use crate::coverage::hir_info::ExtractedHirInfo; use crate::coverage::spans::extract_refined_covspans; @@ -23,10 +24,12 @@ pub(crate) fn extract_mappings_from_mir<'tcx>( hir_info: &ExtractedHirInfo, graph: &CoverageGraph, ) -> ExtractedMappings { + let expn_tree = expansion::build_expn_tree(mir_body, graph); + let mut mappings = vec![]; // Extract ordinary code mappings from MIR statement/terminator spans. - extract_refined_covspans(tcx, mir_body, hir_info, graph, &mut mappings); + extract_refined_covspans(tcx, hir_info, graph, &expn_tree, &mut mappings); extract_branch_mappings(mir_body, hir_info, graph, &mut mappings); diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index 08c7d346009c6..cc9d7c800a96b 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -9,6 +9,7 @@ use crate::coverage::mappings::ExtractedMappings; mod counters; mod expansion; +mod from_mir; mod graph; mod hir_info; mod mappings; diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs index 325935ee84682..ab457401e89d2 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans.rs @@ -1,22 +1,19 @@ -use rustc_middle::mir; use rustc_middle::mir::coverage::{Mapping, MappingKind, START_BCB}; use rustc_middle::ty::TyCtxt; use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, DesugaringKind, ExpnId, ExpnKind, MacroKind, Span}; use tracing::instrument; -use crate::coverage::expansion::{self, ExpnTree, SpanWithBcb}; +use crate::coverage::expansion::{ExpnTree, SpanWithBcb}; +use crate::coverage::from_mir::Hole; use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph}; use crate::coverage::hir_info::ExtractedHirInfo; -use crate::coverage::spans::from_mir::{Hole, RawSpanFromMir}; - -mod from_mir; pub(super) fn extract_refined_covspans<'tcx>( tcx: TyCtxt<'tcx>, - mir_body: &mir::Body<'tcx>, hir_info: &ExtractedHirInfo, graph: &CoverageGraph, + expn_tree: &ExpnTree, mappings: &mut Vec, ) { if hir_info.is_async_fn { @@ -32,14 +29,6 @@ pub(super) fn extract_refined_covspans<'tcx>( let &ExtractedHirInfo { body_span, .. } = hir_info; - let raw_spans = from_mir::extract_raw_spans_from_mir(mir_body, graph); - // Use the raw spans to build a tree of expansions for this function. - let expn_tree = expansion::build_expn_tree( - raw_spans - .into_iter() - .map(|RawSpanFromMir { raw_span, bcb }| SpanWithBcb { span: raw_span, bcb }), - ); - let mut covspans = vec![]; let mut push_covspan = |covspan: Covspan| { let covspan_span = covspan.span; From 075f9c498490977ef9d0f041677fd286ae76809e Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 11 Nov 2025 13:39:19 +1100 Subject: [PATCH 02/24] coverage: Eagerly ensure that span refinement has an expansion tree node This also replaces `push_covspan` with a separate covspan-filtering step, because the relevant code is being reindented anyway. --- .../rustc_mir_transform/src/coverage/spans.rs | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs index ab457401e89d2..4e2831b3af3b8 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans.rs @@ -29,14 +29,32 @@ pub(super) fn extract_refined_covspans<'tcx>( let &ExtractedHirInfo { body_span, .. } = hir_info; + // If there somehow isn't an expansion tree node corresponding to the + // body span, return now and don't create any mappings. + let Some(node) = expn_tree.get(body_span.ctxt().outer_expn()) else { return }; + let mut covspans = vec![]; - let mut push_covspan = |covspan: Covspan| { + + for &SpanWithBcb { span, bcb } in &node.spans { + covspans.push(Covspan { span, bcb }); + } + + // For each expansion with its call-site in the body span, try to + // distill a corresponding covspan. + for &child_expn_id in &node.child_expn_ids { + if let Some(covspan) = single_covspan_for_child_expn(tcx, graph, &expn_tree, child_expn_id) + { + covspans.push(covspan); + } + } + + covspans.retain(|covspan: &Covspan| { let covspan_span = covspan.span; // Discard any spans not contained within the function body span. // Also discard any spans that fill the entire body, because they tend // to represent compiler-inserted code, e.g. implicitly returning `()`. if !body_span.contains(covspan_span) || body_span.source_equal(covspan_span) { - return; + return false; } // Each pushed covspan should have the same context as the body span. @@ -46,27 +64,11 @@ pub(super) fn extract_refined_covspans<'tcx>( false, "span context mismatch: body_span={body_span:?}, covspan.span={covspan_span:?}" ); - return; - } - - covspans.push(covspan); - }; - - if let Some(node) = expn_tree.get(body_span.ctxt().outer_expn()) { - for &SpanWithBcb { span, bcb } in &node.spans { - push_covspan(Covspan { span, bcb }); + return false; } - // For each expansion with its call-site in the body span, try to - // distill a corresponding covspan. - for &child_expn_id in &node.child_expn_ids { - if let Some(covspan) = - single_covspan_for_child_expn(tcx, graph, &expn_tree, child_expn_id) - { - push_covspan(covspan); - } - } - } + true + }); // Only proceed if we found at least one usable span. if covspans.is_empty() { From 696690ba21b6b4739093bddf811499b8daa845b1 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 11 Nov 2025 13:13:33 +1100 Subject: [PATCH 03/24] coverage: Associate hole spans with expansion tree nodes This will make it easier to perform span refinement for child expansions. --- .../src/coverage/expansion.rs | 21 ++++++++++++++- .../src/coverage/from_mir.rs | 16 ----------- .../src/coverage/mappings.rs | 2 +- .../rustc_mir_transform/src/coverage/spans.rs | 27 ++++++++++++------- 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/expansion.rs b/compiler/rustc_mir_transform/src/coverage/expansion.rs index df27b1ccabe1f..827df27da52c7 100644 --- a/compiler/rustc_mir_transform/src/coverage/expansion.rs +++ b/compiler/rustc_mir_transform/src/coverage/expansion.rs @@ -5,6 +5,7 @@ use rustc_span::{ExpnId, ExpnKind, Span}; use crate::coverage::from_mir; use crate::coverage::graph::CoverageGraph; +use crate::coverage::hir_info::ExtractedHirInfo; #[derive(Clone, Copy, Debug)] pub(crate) struct SpanWithBcb { @@ -74,6 +75,10 @@ pub(crate) struct ExpnNode { pub(crate) spans: Vec, /// Expansions whose call-site is in this expansion. pub(crate) child_expn_ids: FxIndexSet, + + /// Hole spans belonging to this expansion, to be carved out from the + /// code spans during span refinement. + pub(crate) hole_spans: Vec, } impl ExpnNode { @@ -92,13 +97,19 @@ impl ExpnNode { spans: vec![], child_expn_ids: FxIndexSet::default(), + + hole_spans: vec![], } } } /// Extracts raw span/BCB pairs from potentially-different syntax contexts, and /// arranges them into an "expansion tree" based on their expansion call-sites. -pub(crate) fn build_expn_tree(mir_body: &mir::Body<'_>, graph: &CoverageGraph) -> ExpnTree { +pub(crate) fn build_expn_tree( + mir_body: &mir::Body<'_>, + hir_info: &ExtractedHirInfo, + graph: &CoverageGraph, +) -> ExpnTree { let raw_spans = from_mir::extract_raw_spans_from_mir(mir_body, graph); let mut nodes = FxIndexMap::default(); @@ -131,5 +142,13 @@ pub(crate) fn build_expn_tree(mir_body: &mir::Body<'_>, graph: &CoverageGraph) - } } + // Associate each hole span (extracted from HIR) with its corresponding + // expansion tree node. + for &hole_span in &hir_info.hole_spans { + let expn_id = hole_span.ctxt().outer_expn(); + let Some(node) = nodes.get_mut(&expn_id) else { continue }; + node.hole_spans.push(hole_span); + } + ExpnTree { nodes } } diff --git a/compiler/rustc_mir_transform/src/coverage/from_mir.rs b/compiler/rustc_mir_transform/src/coverage/from_mir.rs index c096f1e2632ce..e1623f590a8eb 100644 --- a/compiler/rustc_mir_transform/src/coverage/from_mir.rs +++ b/compiler/rustc_mir_transform/src/coverage/from_mir.rs @@ -142,19 +142,3 @@ fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option { | TerminatorKind::InlineAsm { .. } => Some(terminator.source_info.span), } } - -#[derive(Debug)] -pub(crate) struct Hole { - pub(crate) span: Span, -} - -impl Hole { - pub(crate) fn merge_if_overlapping_or_adjacent(&mut self, other: &mut Self) -> bool { - if !self.span.overlaps_or_adjacent(other.span) { - return false; - } - - self.span = self.span.to(other.span); - true - } -} diff --git a/compiler/rustc_mir_transform/src/coverage/mappings.rs b/compiler/rustc_mir_transform/src/coverage/mappings.rs index f632cd02d8326..5347e1150e5de 100644 --- a/compiler/rustc_mir_transform/src/coverage/mappings.rs +++ b/compiler/rustc_mir_transform/src/coverage/mappings.rs @@ -24,7 +24,7 @@ pub(crate) fn extract_mappings_from_mir<'tcx>( hir_info: &ExtractedHirInfo, graph: &CoverageGraph, ) -> ExtractedMappings { - let expn_tree = expansion::build_expn_tree(mir_body, graph); + let expn_tree = expansion::build_expn_tree(mir_body, hir_info, graph); let mut mappings = vec![]; diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs index 4e2831b3af3b8..2a06b409e8c2d 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans.rs @@ -5,7 +5,6 @@ use rustc_span::{BytePos, DesugaringKind, ExpnId, ExpnKind, MacroKind, Span}; use tracing::instrument; use crate::coverage::expansion::{ExpnTree, SpanWithBcb}; -use crate::coverage::from_mir::Hole; use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph}; use crate::coverage::hir_info::ExtractedHirInfo; @@ -98,14 +97,8 @@ pub(super) fn extract_refined_covspans<'tcx>( covspans.dedup_by(|b, a| a.span.source_equal(b.span)); // Sort the holes, and merge overlapping/adjacent holes. - let mut holes = hir_info - .hole_spans - .iter() - .copied() - // Discard any holes that aren't directly visible within the body span. - .filter(|&hole_span| body_span.contains(hole_span) && body_span.eq_ctxt(hole_span)) - .map(|span| Hole { span }) - .collect::>(); + let mut holes = node.hole_spans.iter().copied().map(|span| Hole { span }).collect::>(); + holes.sort_by(|a, b| compare_spans(a.span, b.span)); holes.dedup_by(|b, a| a.merge_if_overlapping_or_adjacent(b)); @@ -286,3 +279,19 @@ fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option { }) .ok()? } + +#[derive(Debug)] +struct Hole { + span: Span, +} + +impl Hole { + fn merge_if_overlapping_or_adjacent(&mut self, other: &mut Self) -> bool { + if !self.span.overlaps_or_adjacent(other.span) { + return false; + } + + self.span = self.span.to(other.span); + true + } +} From 06e2e7aae1bbf6eda8d8e2a1917396dc3df0f8f8 Mon Sep 17 00:00:00 2001 From: Bart Jacobs Date: Tue, 11 Nov 2025 17:18:28 +0100 Subject: [PATCH 04/24] CStr docs: Fix CStr vs &CStr confusion The CStr docs used to say things about CStr that are only true for &CStr. Also, it's the bytes that are being borrowed, not the reference. One could say that it's the reference that is doing the borrowing, rather than being borrowed. --- library/core/src/ffi/c_str.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs index 09d9b160700ca..6acabdda69543 100644 --- a/library/core/src/ffi/c_str.rs +++ b/library/core/src/ffi/c_str.rs @@ -15,18 +15,18 @@ use crate::{fmt, ops, slice, str}; // actually reference libstd or liballoc in intra-doc links. so, the best we can do is remove the // links to `CString` and `String` for now until a solution is developed -/// Representation of a borrowed C string. +/// The `&CStr` type represents a borrowed C string. /// -/// This type represents a borrowed reference to a nul-terminated +/// Type `&CStr` represents a reference to a borrowed nul-terminated /// array of bytes. It can be constructed safely from a &[[u8]] /// slice, or unsafely from a raw `*const c_char`. It can be expressed as a /// literal in the form `c"Hello world"`. /// -/// The `CStr` can then be converted to a Rust &[str] by performing +/// The `&CStr` can then be converted to a Rust &[str] by performing /// UTF-8 validation, or into an owned `CString`. /// /// `&CStr` is to `CString` as &[str] is to `String`: the former -/// in each pair are borrowed references; the latter are owned +/// in each pair are borrowing references; the latter are owned /// strings. /// /// Note that this structure does **not** have a guaranteed layout (the `repr(transparent)` From a56fc9c21366b8430a779975638477fdb00b2715 Mon Sep 17 00:00:00 2001 From: Bart Jacobs Date: Tue, 11 Nov 2025 19:56:48 +0100 Subject: [PATCH 05/24] Some tweaks --- library/core/src/ffi/c_str.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs index 6acabdda69543..9a35ed07b89ac 100644 --- a/library/core/src/ffi/c_str.rs +++ b/library/core/src/ffi/c_str.rs @@ -15,9 +15,9 @@ use crate::{fmt, ops, slice, str}; // actually reference libstd or liballoc in intra-doc links. so, the best we can do is remove the // links to `CString` and `String` for now until a solution is developed -/// The `&CStr` type represents a borrowed C string. +/// A dynamically-sized view of a C string. /// -/// Type `&CStr` represents a reference to a borrowed nul-terminated +/// The type `&CStr` represents a reference to a borrowed nul-terminated /// array of bytes. It can be constructed safely from a &[[u8]] /// slice, or unsafely from a raw `*const c_char`. It can be expressed as a /// literal in the form `c"Hello world"`. From 65b5d765bc08141c0a01b29611a8660490ef2d14 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 11 Nov 2025 16:18:50 -0800 Subject: [PATCH 06/24] Implement `Read::read_array` Tracking issue: https://github.com/rust-lang/rust/issues/148848 --- library/std/src/io/mod.rs | 40 ++++++++++++++++++++++++++++++++++++++- library/std/src/lib.rs | 1 + 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 25a4661a0bc9c..499b094735247 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -330,7 +330,7 @@ pub use self::{ stdio::{Stderr, StderrLock, Stdin, StdinLock, Stdout, StdoutLock, stderr, stdin, stdout}, util::{Empty, Repeat, Sink, empty, repeat, sink}, }; -use crate::mem::take; +use crate::mem::{MaybeUninit, take}; use crate::ops::{Deref, DerefMut}; use crate::{cmp, fmt, slice, str, sys}; @@ -1242,6 +1242,44 @@ pub trait Read { { Take { inner: self, len: limit, limit } } + + /// Read and return a fixed array of bytes from this source. + /// + /// This function uses an array sized based on a const generic size known at compile time. You + /// can specify the size with turbofish (`reader.read_array::<8>()`), or let type inference + /// determine the number of bytes needed based on how the return value gets used. For instance, + /// this function works well with functions like [`u64::from_le_bytes`] to turn an array of + /// bytes into an integer of the same size. + /// + /// Like `read_exact`, if this function encounters an "end of file" before reading the desired + /// number of bytes, it returns an error of the kind [`ErrorKind::UnexpectedEof`]. + /// + /// ``` + /// #![feature(read_array)] + /// use std::io::Cursor; + /// use std::io::prelude::*; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buf = Cursor::new([1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2]); + /// let x = u64::from_le_bytes(buf.read_array()?); + /// let y = u32::from_be_bytes(buf.read_array()?); + /// let z = u16::from_be_bytes(buf.read_array()?); + /// assert_eq!(x, 0x807060504030201); + /// assert_eq!(y, 0x9080706); + /// assert_eq!(z, 0x504); + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "read_array", issue = "148848")] + fn read_array(&mut self) -> Result<[u8; N]> + where + Self: Sized, + { + let mut buf = [MaybeUninit::uninit(); N]; + let mut borrowed_buf = BorrowedBuf::from(buf.as_mut_slice()); + self.read_buf_exact(borrowed_buf.unfilled())?; + Ok(unsafe { MaybeUninit::array_assume_init(buf) }) + } } /// Reads all bytes from a [reader][Read] into a new [`String`]. diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 7b6cfbfe0f259..5374e9e61783f 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -348,6 +348,7 @@ #![feature(int_from_ascii)] #![feature(ip)] #![feature(lazy_get)] +#![feature(maybe_uninit_array_assume_init)] #![feature(maybe_uninit_slice)] #![feature(maybe_uninit_write_slice)] #![feature(panic_can_unwind)] From a0fe930898e21320771c66141b1c771b690088a8 Mon Sep 17 00:00:00 2001 From: edwloef Date: Wed, 12 Nov 2025 15:06:54 +0100 Subject: [PATCH 07/24] Refactor `Box::take` --- library/alloc/src/boxed.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index fde6b80a62fe6..2b767ffe02bee 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -725,9 +725,9 @@ impl Box { #[unstable(feature = "box_take", issue = "147212")] pub fn take(boxed: Self) -> (T, Box, A>) { unsafe { - let (raw, alloc) = Box::into_raw_with_allocator(boxed); + let (raw, alloc) = Box::into_non_null_with_allocator(boxed); let value = raw.read(); - let uninit = Box::from_raw_in(raw.cast::>(), alloc); + let uninit = Box::from_non_null_in(raw.cast_uninit(), alloc); (value, uninit) } } From 8fec082dbce409908ddf81a9935c14d8230af57f Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 17 Oct 2025 12:40:48 +0200 Subject: [PATCH 08/24] ignore `build-rust-analyzer` even if it's a symlink --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0030f22363c24..91a2647ca98f4 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,7 @@ no_llvm_build /llvm/ /mingw-build/ /build -/build-rust-analyzer/ +/build-rust-analyzer /dist/ /unicode-downloads /target From c7e50d0f377b2cbc5a9f5706992121301ce44533 Mon Sep 17 00:00:00 2001 From: Quinn Okabayashi Date: Wed, 12 Nov 2025 15:46:08 +0000 Subject: [PATCH 09/24] Remove unused LLVMModuleRef argument --- compiler/rustc_codegen_llvm/src/back/write.rs | 2 +- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 2 +- compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index c55fe3fad008a..fde7dd6ef7a85 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -683,7 +683,7 @@ pub(crate) unsafe fn llvm_optimize( // Here we map the old arguments to the new arguments, with an offset of 1 to make sure // that we don't use the newly added `%dyn_ptr`. unsafe { - llvm::LLVMRustOffloadMapper(cx.llmod(), old_fn, new_fn); + llvm::LLVMRustOffloadMapper(old_fn, new_fn); } llvm::set_linkage(new_fn, llvm::get_linkage(old_fn)); diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 16549f9aab81a..ca64d96c2a33c 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -2025,7 +2025,7 @@ unsafe extern "C" { ) -> &Attribute; // Operations on functions - pub(crate) fn LLVMRustOffloadMapper<'a>(M: &'a Module, Fn: &'a Value, Fn: &'a Value); + pub(crate) fn LLVMRustOffloadMapper<'a>(Fn: &'a Value, Fn: &'a Value); pub(crate) fn LLVMRustGetOrInsertFunction<'a>( M: &'a Module, Name: *const c_char, diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index eab65f92e2935..8823c83922822 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -144,9 +144,7 @@ extern "C" void LLVMRustPrintStatistics(RustStringRef OutBuf) { llvm::PrintStatistics(OS); } -extern "C" void LLVMRustOffloadMapper(LLVMModuleRef M, LLVMValueRef OldFn, - LLVMValueRef NewFn) { - llvm::Module *module = llvm::unwrap(M); +extern "C" void LLVMRustOffloadMapper(LLVMValueRef OldFn, LLVMValueRef NewFn) { llvm::Function *oldFn = llvm::unwrap(OldFn); llvm::Function *newFn = llvm::unwrap(NewFn); From 258a446c898dbcba98b95a0759e019aa366cb8b6 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 11 Nov 2025 11:07:01 +1100 Subject: [PATCH 10/24] Simplify `Resolver::resolve_macro_path`. There are only two call sites, and three of the arguments are identical at both call sites. This commit removes those arguments and renames the method accordingly. --- compiler/rustc_resolve/src/ident.rs | 5 +---- compiler/rustc_resolve/src/macros.rs | 16 +++++----------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index f3f0a74d03bc0..8ecae07dea67d 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -458,14 +458,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let mut result = Err(Determinacy::Determined); for derive in parent_scope.derives { let parent_scope = &ParentScope { derives: &[], ..*parent_scope }; - match this.reborrow().resolve_macro_path( + match this.reborrow().resolve_derive_macro_path( derive, - MacroKind::Derive, parent_scope, - true, force, ignore_import, - None, ) { Ok((Some(ext), _)) => { if ext.helper_attrs.contains(&ident.name) { diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 4a5894c9ffa86..d40752d21c1ef 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -395,14 +395,11 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> { for (i, resolution) in entry.resolutions.iter_mut().enumerate() { if resolution.exts.is_none() { resolution.exts = Some( - match self.cm().resolve_macro_path( + match self.cm().resolve_derive_macro_path( &resolution.path, - MacroKind::Derive, &parent_scope, - true, force, None, - None, ) { Ok((Some(ext), _)) => { if !ext.helper_attrs.is_empty() { @@ -706,26 +703,23 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Ok((ext, res)) } - pub(crate) fn resolve_macro_path<'r>( + pub(crate) fn resolve_derive_macro_path<'r>( self: CmResolver<'r, 'ra, 'tcx>, path: &ast::Path, - kind: MacroKind, parent_scope: &ParentScope<'ra>, - trace: bool, force: bool, ignore_import: Option>, - suggestion_span: Option, ) -> Result<(Option>, Res), Determinacy> { self.resolve_macro_or_delegation_path( path, - kind, + MacroKind::Derive, parent_scope, - trace, + true, force, None, None, ignore_import, - suggestion_span, + None, ) } From 8ece93912c493990e5533eca4c3874db8ece9466 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 11 Nov 2025 11:13:50 +1100 Subject: [PATCH 11/24] Remove `trace` argument from `resolve_macro_or_delegation_path`. It's `true` at all call sites. --- compiler/rustc_resolve/src/macros.rs | 37 +++++++++++----------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index d40752d21c1ef..4fed69071e58f 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -561,7 +561,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { path, kind, parent_scope, - true, force, deleg_impl, invoc_in_mod_inert_attr.map(|def_id| (def_id, node_id)), @@ -714,7 +713,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { path, MacroKind::Derive, parent_scope, - true, force, None, None, @@ -728,7 +726,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ast_path: &ast::Path, kind: MacroKind, parent_scope: &ParentScope<'ra>, - trace: bool, force: bool, deleg_impl: Option, invoc_in_mod_inert_attr: Option<(LocalDefId, NodeId)>, @@ -767,16 +764,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { PathResult::Module(..) => unreachable!(), }; - if trace { - self.multi_segment_macro_resolutions.borrow_mut(&self).push(( - path, - path_span, - kind, - *parent_scope, - res.ok(), - ns, - )); - } + self.multi_segment_macro_resolutions.borrow_mut(&self).push(( + path, + path_span, + kind, + *parent_scope, + res.ok(), + ns, + )); self.prohibit_imported_non_macro_attrs(None, res.ok(), path_span); res @@ -794,15 +789,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { return Err(Determinacy::Undetermined); } - if trace { - self.single_segment_macro_resolutions.borrow_mut(&self).push(( - path[0].ident, - kind, - *parent_scope, - binding.ok(), - suggestion_span, - )); - } + self.single_segment_macro_resolutions.borrow_mut(&self).push(( + path[0].ident, + kind, + *parent_scope, + binding.ok(), + suggestion_span, + )); let res = binding.map(|binding| binding.res()); self.prohibit_imported_non_macro_attrs(binding.ok(), res.ok(), path_span); From b728064935ac5a6a534ffc1f92ac8f83f134ac4b Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 13 Nov 2025 09:10:15 +1100 Subject: [PATCH 12/24] Add a helpful comment to `DeriveResolution::exts`. --- compiler/rustc_expand/src/base.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 810a5a21a055b..946f17943fe3d 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -1099,6 +1099,9 @@ pub struct Indeterminate; pub struct DeriveResolution { pub path: ast::Path, pub item: Annotatable, + // FIXME: currently this field is only used in `is_none`/`is_some` conditions. However, the + // `Arc` will be used if the FIXME in `MacroExpander::fully_expand_fragment` + // is completed. pub exts: Option>, pub is_const: bool, } From 199f30844688baf711cddd16edc7e45802893346 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Wed, 12 Nov 2025 23:12:37 -0800 Subject: [PATCH 13/24] Guard against incorrect `read_buf_exact` implementations --- library/std/src/io/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 499b094735247..b7756befa11e9 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -1278,6 +1278,8 @@ pub trait Read { let mut buf = [MaybeUninit::uninit(); N]; let mut borrowed_buf = BorrowedBuf::from(buf.as_mut_slice()); self.read_buf_exact(borrowed_buf.unfilled())?; + // Guard against incorrect `read_buf_exact` implementations. + assert_eq!(borrowed_buf.len(), N); Ok(unsafe { MaybeUninit::array_assume_init(buf) }) } } From a40c3e5b4b63bd7edc9a9f0a3b24bfe9452c5869 Mon Sep 17 00:00:00 2001 From: Sinan Nalkaya Date: Thu, 13 Nov 2025 11:53:58 +0100 Subject: [PATCH 14/24] Disable rustdoc-test-builder test partially for SGX target. --- tests/run-make/rustdoc-test-builder/rmake.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/run-make/rustdoc-test-builder/rmake.rs b/tests/run-make/rustdoc-test-builder/rmake.rs index d10a3c92cae42..17d40c68fd922 100644 --- a/tests/run-make/rustdoc-test-builder/rmake.rs +++ b/tests/run-make/rustdoc-test-builder/rmake.rs @@ -25,7 +25,10 @@ fn main() { // Some targets (for example wasm) cannot execute doctests directly even with a runner, // so only exercise the success path when the target can run on the host. - if target().contains("wasm") || std::env::var_os("REMOTE_TEST_CLIENT").is_some() { + if target().contains("wasm") + || target().contains("sgx") + || std::env::var_os("REMOTE_TEST_CLIENT").is_some() + { return; } From 776405c058851a9d5937c44509174d2f0198b987 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 13 Nov 2025 12:46:34 +0100 Subject: [PATCH 15/24] add missing s390x target feature to std detect test --- library/std/tests/run-time-detect.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/std/tests/run-time-detect.rs b/library/std/tests/run-time-detect.rs index b2c3d0d3f9f8b..be2980f73268f 100644 --- a/library/std/tests/run-time-detect.rs +++ b/library/std/tests/run-time-detect.rs @@ -16,6 +16,7 @@ all(target_arch = "powerpc64", target_os = "linux"), feature(stdarch_powerpc_feature_detection) )] +#![cfg_attr(all(target_arch = "s390x", target_os = "linux"), feature(s390x_target_feature))] #[test] #[cfg(all(target_arch = "arm", any(target_os = "linux", target_os = "android")))] From abaccaed216a9aaeb58cd4d5bd2b8d38e79dd8c6 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 13 Nov 2025 14:35:06 +0100 Subject: [PATCH 16/24] waffle: stop watching codegen ssa --- triagebot.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/triagebot.toml b/triagebot.toml index adc73c42c428c..0bf310e8fd7ef 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -922,9 +922,6 @@ cc = ["@davidtwco", "@wesleywiser"] [mentions."compiler/rustc_codegen_cranelift"] cc = ["@bjorn3"] -[mentions."compiler/rustc_codegen_ssa"] -cc = ["@WaffleLapkin"] - [mentions."compiler/rustc_codegen_gcc"] cc = ["@antoyo", "@GuillaumeGomez"] From 78beefed8483cc7b33cf678b6408927dcf5ffb8b Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 12 Nov 2025 20:04:38 +0100 Subject: [PATCH 17/24] error when ABI does not support guaranteed tail calls --- compiler/rustc_abi/src/extern_abi.rs | 45 +++++++++++++++++++ .../rustc_mir_build/src/check_tail_calls.rs | 14 ++++++ .../unsupported-abi/cmse-nonsecure-call.rs | 38 ++++++++++++++++ .../cmse-nonsecure-call.stderr | 34 ++++++++++++++ .../unsupported-abi/cmse-nonsecure-entry.rs | 22 +++++++++ .../cmse-nonsecure-entry.stderr | 10 +++++ 6 files changed, 163 insertions(+) create mode 100644 tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.rs create mode 100644 tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.stderr create mode 100644 tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.rs create mode 100644 tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.stderr diff --git a/compiler/rustc_abi/src/extern_abi.rs b/compiler/rustc_abi/src/extern_abi.rs index e3b2b1eff72d4..0625759359ea3 100644 --- a/compiler/rustc_abi/src/extern_abi.rs +++ b/compiler/rustc_abi/src/extern_abi.rs @@ -276,6 +276,51 @@ impl ExternAbi { _ => CVariadicStatus::NotSupported, } } + + /// Returns whether the ABI supports guaranteed tail calls. + #[cfg(feature = "nightly")] + pub fn supports_guaranteed_tail_call(self) -> bool { + match self { + Self::CmseNonSecureCall | Self::CmseNonSecureEntry => { + // See https://godbolt.org/z/9jhdeqErv. The CMSE calling conventions clear registers + // before returning, and hence cannot guarantee a tail call. + false + } + Self::AvrInterrupt + | Self::AvrNonBlockingInterrupt + | Self::Msp430Interrupt + | Self::RiscvInterruptM + | Self::RiscvInterruptS + | Self::X86Interrupt => { + // See https://godbolt.org/z/Edfjnxxcq. Interrupts cannot be called directly. + false + } + Self::GpuKernel | Self::PtxKernel => { + // See https://godbolt.org/z/jq5TE5jK1. + false + } + Self::Custom => { + // This ABI does not support calls at all (except via assembly). + false + } + Self::C { .. } + | Self::System { .. } + | Self::Rust + | Self::RustCall + | Self::RustCold + | Self::RustInvalid + | Self::Unadjusted + | Self::EfiApi + | Self::Aapcs { .. } + | Self::Cdecl { .. } + | Self::Stdcall { .. } + | Self::Fastcall { .. } + | Self::Thiscall { .. } + | Self::Vectorcall { .. } + | Self::SysV64 { .. } + | Self::Win64 { .. } => true, + } + } } pub fn all_names() -> Vec<&'static str> { diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index 9115c17f37525..b8547e288027a 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -135,6 +135,10 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { self.report_abi_mismatch(expr.span, caller_sig.abi, callee_sig.abi); } + if !callee_sig.abi.supports_guaranteed_tail_call() { + self.report_unsupported_abi(expr.span, callee_sig.abi); + } + // FIXME(explicit_tail_calls): this currently fails for cases where opaques are used. // e.g. // ``` @@ -358,6 +362,16 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { self.found_errors = Err(err); } + fn report_unsupported_abi(&mut self, sp: Span, callee_abi: ExternAbi) { + let err = self + .tcx + .dcx() + .struct_span_err(sp, "ABI does not support guaranteed tail calls") + .with_note(format!("`become` is not supported for `extern {callee_abi}` functions")) + .emit(); + self.found_errors = Err(err); + } + fn report_signature_mismatch( &mut self, sp: Span, diff --git a/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.rs b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.rs new file mode 100644 index 0000000000000..028716a14c7b4 --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.rs @@ -0,0 +1,38 @@ +//@ add-minicore +//@ ignore-backends: gcc +//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib +//@ needs-llvm-components: arm +#![expect(incomplete_features)] +#![feature(no_core, explicit_tail_calls, abi_cmse_nonsecure_call)] +#![no_core] + +extern crate minicore; +use minicore::*; + +unsafe extern "C" { + safe fn magic() -> extern "cmse-nonsecure-call" fn(u32, u32) -> u32; +} + +// The `cmse-nonsecure-call` ABI can only occur on function pointers: +// +// - a `cmse-nonsecure-call` definition throws an error +// - a `cmse-nonsecure-call` become in a definition with any other ABI is an ABI mismatch +#[no_mangle] +extern "cmse-nonsecure-call" fn become_nonsecure_call_1(x: u32, y: u32) -> u32 { + //~^ ERROR the `"cmse-nonsecure-call"` ABI is only allowed on function pointers + unsafe { + let f = magic(); + become f(1, 2) + //~^ ERROR ABI does not support guaranteed tail calls + } +} + +#[no_mangle] +extern "C" fn become_nonsecure_call_2(x: u32, y: u32) -> u32 { + unsafe { + let f = magic(); + become f(1, 2) + //~^ ERROR mismatched function ABIs + //~| ERROR ABI does not support guaranteed tail calls + } +} diff --git a/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.stderr b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.stderr new file mode 100644 index 0000000000000..08f11a4bd4b71 --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.stderr @@ -0,0 +1,34 @@ +error[E0781]: the `"cmse-nonsecure-call"` ABI is only allowed on function pointers + --> $DIR/cmse-nonsecure-call.rs:21:1 + | +LL | extern "cmse-nonsecure-call" fn become_nonsecure_call_1(x: u32, y: u32) -> u32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: ABI does not support guaranteed tail calls + --> $DIR/cmse-nonsecure-call.rs:25:9 + | +LL | become f(1, 2) + | ^^^^^^^^^^^^^^ + | + = note: `become` is not supported for `extern "cmse-nonsecure-call"` functions + +error: mismatched function ABIs + --> $DIR/cmse-nonsecure-call.rs:34:9 + | +LL | become f(1, 2) + | ^^^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have the same ABI + = note: caller ABI is `"C"`, while callee ABI is `"cmse-nonsecure-call"` + +error: ABI does not support guaranteed tail calls + --> $DIR/cmse-nonsecure-call.rs:34:9 + | +LL | become f(1, 2) + | ^^^^^^^^^^^^^^ + | + = note: `become` is not supported for `extern "cmse-nonsecure-call"` functions + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0781`. diff --git a/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.rs b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.rs new file mode 100644 index 0000000000000..006fa538c4891 --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.rs @@ -0,0 +1,22 @@ +//@ add-minicore +//@ ignore-backends: gcc +//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib +//@ needs-llvm-components: arm +#![expect(incomplete_features)] +#![feature(no_core, explicit_tail_calls, cmse_nonsecure_entry)] +#![no_core] + +extern crate minicore; +use minicore::*; + +#[inline(never)] +extern "cmse-nonsecure-entry" fn entry(c: bool, x: u32, y: u32) -> u32 { + if c { x } else { y } +} + +// A `cmse-nonsecure-entry` clears registers before returning, so a tail call cannot be guaranteed. +#[unsafe(no_mangle)] +extern "cmse-nonsecure-entry" fn become_nonsecure_entry(c: bool, x: u32, y: u32) -> u32 { + become entry(c, x, y) + //~^ ERROR ABI does not support guaranteed tail calls +} diff --git a/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.stderr b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.stderr new file mode 100644 index 0000000000000..3acbe8c5bfaa5 --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.stderr @@ -0,0 +1,10 @@ +error: ABI does not support guaranteed tail calls + --> $DIR/cmse-nonsecure-entry.rs:20:5 + | +LL | become entry(c, x, y) + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: `become` is not supported for `extern "cmse-nonsecure-entry"` functions + +error: aborting due to 1 previous error + From 101ef2bf81ef8715fa123bdde4d139f158efe578 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 5 Nov 2025 17:50:21 +0100 Subject: [PATCH 18/24] Correctly link to associated trait items in reexports --- src/librustdoc/html/format.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 4843c20c758e6..b7f6d84ea36c2 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -838,9 +838,23 @@ fn print_higher_ranked_params_with_space( pub(crate) fn print_anchor(did: DefId, text: Symbol, cx: &Context<'_>) -> impl Display { fmt::from_fn(move |f| { if let Ok(HrefInfo { url, kind, rust_path }) = href(did, cx) { + let tcx = cx.tcx(); + let def_kind = tcx.def_kind(did); + let anchor = if matches!( + def_kind, + DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant + ) { + let parent_def_id = tcx.parent(did); + let item_type = + ItemType::from_def_kind(def_kind, Some(tcx.def_kind(parent_def_id))); + format!("#{}.{}", item_type.as_str(), tcx.item_name(did)) + } else { + String::new() + }; + write!( f, - r#"{text}"#, + r#"{text}"#, path = join_path_syms(rust_path), text = EscapeBodyText(text.as_str()), ) From 044245c6c8891ae480a4f30bd0a9331966987cf2 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 5 Nov 2025 17:51:27 +0100 Subject: [PATCH 19/24] Add regression test for #148008 --- .../import_trait_associated_functions.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/rustdoc/import_trait_associated_functions.rs diff --git a/tests/rustdoc/import_trait_associated_functions.rs b/tests/rustdoc/import_trait_associated_functions.rs new file mode 100644 index 0000000000000..84f6f8009701d --- /dev/null +++ b/tests/rustdoc/import_trait_associated_functions.rs @@ -0,0 +1,19 @@ +// This test ensures that reexports of associated items links to the associated items. +// Regression test for . + +#![feature(import_trait_associated_functions)] + +#![crate_name = "foo"] + +//@ has 'foo/index.html' + +pub trait Test { + fn method(); + const CONST: u8; + type Type; +} + +//@ has - '//*[@id="reexport.method"]//a[@href="trait.Test.html#tymethod.method"]' 'method' +//@ has - '//*[@id="reexport.CONST"]//a[@href="trait.Test.html#associatedconstant.CONST"]' 'CONST' +//@ has - '//*[@id="reexport.Type"]//a[@href="trait.Test.html#associatedtype.Type"]' 'Type' +pub use self::Test::{method, CONST, Type}; From ad1789a5f0a09f4d1c5a29cc6df9960bce97797a Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 13 Nov 2025 15:57:51 +0100 Subject: [PATCH 20/24] Expose fmt::Arguments::from_str as unstable. --- library/alloc/src/lib.rs | 1 + library/core/src/fmt/mod.rs | 22 ++++++++++++++-------- library/core/src/macros/mod.rs | 6 +++--- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 666ae27fb8634..dc58b30afbce3 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -116,6 +116,7 @@ #![feature(exact_size_is_empty)] #![feature(extend_one)] #![feature(extend_one_unchecked)] +#![feature(fmt_arguments_from_str)] #![feature(fmt_internals)] #![feature(fn_traits)] #![feature(formatting_options)] diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index e00e48bcfeb70..7ca33d363cd8f 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -734,7 +734,21 @@ impl<'a> Arguments<'a> { unsafe { Arguments { template: mem::transmute(template), args: mem::transmute(args) } } } + // Same as `from_str`, but not const. + // Used by format_args!() expansion when arguments are inlined, + // e.g. format_args!("{}", 123), which is not allowed in const. #[inline] + pub fn from_str_nonconst(s: &'static str) -> Arguments<'a> { + Arguments::from_str(s) + } +} + +impl<'a> Arguments<'a> { + /// Create a `fmt::Arguments` object for a single static string. + /// + /// Formatting this `fmt::Arguments` will just produce the string as-is. + #[inline] + #[unstable(feature = "fmt_arguments_from_str", issue = "148905")] pub const fn from_str(s: &'static str) -> Arguments<'a> { // SAFETY: This is the "static str" representation of fmt::Arguments; see above. unsafe { @@ -744,14 +758,6 @@ impl<'a> Arguments<'a> { } } } - - // Same as `from_str`, but not const. - // Used by format_args!() expansion when arguments are inlined, - // e.g. format_args!("{}", 123), which is not allowed in const. - #[inline] - pub fn from_str_nonconst(s: &'static str) -> Arguments<'a> { - Arguments::from_str(s) - } } #[doc(hidden)] diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index df24dd43b82eb..6156525b2f599 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -991,7 +991,7 @@ pub(crate) mod builtin { #[stable(feature = "rust1", since = "1.0.0")] #[rustc_diagnostic_item = "format_args_macro"] #[allow_internal_unsafe] - #[allow_internal_unstable(fmt_internals)] + #[allow_internal_unstable(fmt_internals, fmt_arguments_from_str)] #[rustc_builtin_macro] #[macro_export] macro_rules! format_args { @@ -1005,7 +1005,7 @@ pub(crate) mod builtin { /// /// This macro will be removed once `format_args` is allowed in const contexts. #[unstable(feature = "const_format_args", issue = "none")] - #[allow_internal_unstable(fmt_internals, const_fmt_arguments_new)] + #[allow_internal_unstable(fmt_internals, fmt_arguments_from_str)] #[rustc_builtin_macro] #[macro_export] macro_rules! const_format_args { @@ -1020,7 +1020,7 @@ pub(crate) mod builtin { reason = "`format_args_nl` is only for internal \ language use and is subject to change" )] - #[allow_internal_unstable(fmt_internals)] + #[allow_internal_unstable(fmt_internals, fmt_arguments_from_str)] #[rustc_builtin_macro] #[doc(hidden)] #[macro_export] From ddebb6269f53d16465f1b49484527a8cd638abbd Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 13 Nov 2025 16:05:09 +0100 Subject: [PATCH 21/24] add assembly test for infinite recursion with `become` --- .../tail-call-infinite-recursion.rs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/assembly-llvm/tail-call-infinite-recursion.rs diff --git a/tests/assembly-llvm/tail-call-infinite-recursion.rs b/tests/assembly-llvm/tail-call-infinite-recursion.rs new file mode 100644 index 0000000000000..f788f0d590b6b --- /dev/null +++ b/tests/assembly-llvm/tail-call-infinite-recursion.rs @@ -0,0 +1,21 @@ +//@ add-minicore +//@ assembly-output: emit-asm +//@ compile-flags: --target x86_64-unknown-linux-gnu -Copt-level=0 -Cllvm-args=-x86-asm-syntax=intel +//@ needs-llvm-components: x86 +#![expect(incomplete_features)] +#![feature(no_core, explicit_tail_calls)] +#![crate_type = "lib"] +#![no_core] + +extern crate minicore; +use minicore::*; + +// Test that an infinite loop via guaranteed tail calls does not blow the stack. + +// CHECK-LABEL: inf +// CHECK: mov rax, qword ptr [rip + inf@GOTPCREL] +// CHECK: jmp rax +#[unsafe(no_mangle)] +fn inf() -> ! { + become inf() +} From 5d33ab13166918cac5abd5f8be4ffd33b0619b31 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 13 Nov 2025 16:17:52 +0100 Subject: [PATCH 22/24] fix some typos in `!`-related test comments --- tests/mir-opt/uninhabited_not_read.rs | 2 +- tests/ui/consts/const-eval/raw-bytes.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/mir-opt/uninhabited_not_read.rs b/tests/mir-opt/uninhabited_not_read.rs index 15769cdd75b3f..4c80f5e55c684 100644 --- a/tests/mir-opt/uninhabited_not_read.rs +++ b/tests/mir-opt/uninhabited_not_read.rs @@ -1,7 +1,7 @@ // skip-filecheck //@ edition: 2021 -// In ed 2021 and below, we don't fallback `!` to `()`. +// In ed 2021 and below, we fallback `!` to `()`. // This would introduce a `! -> ()` coercion which would // be UB if we didn't disallow this explicitly. diff --git a/tests/ui/consts/const-eval/raw-bytes.rs b/tests/ui/consts/const-eval/raw-bytes.rs index 58ae763e017f0..199f4d1ea0e10 100644 --- a/tests/ui/consts/const-eval/raw-bytes.rs +++ b/tests/ui/consts/const-eval/raw-bytes.rs @@ -34,7 +34,7 @@ const BAD_ENUM2: Enum2 = unsafe { mem::transmute(0usize) }; #[derive(Copy, Clone)] enum Never {} -// An enum with 3 variants of which some are uninhabited -- so the uninhabited variants *do* +// An enum with 4 variants of which only some are uninhabited -- so the uninhabited variants *do* // have a discriminant. enum UninhDiscriminant { A, From b13f49e419d981eaed7d504217662d84aba1c049 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 13 Nov 2025 16:17:52 +0100 Subject: [PATCH 23/24] add explanation comments to `!`-related tests ... outside `tests/ui/never_type/` --- tests/mir-opt/uninhabited_enum.rs | 3 ++ tests/ui/coercion/coerce-loop-issue-122561.rs | 4 ++- .../coercion/coerce-loop-issue-122561.stderr | 32 +++++++++---------- .../let-irrefutable-pattern-ice-120337.rs | 10 +++++- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/tests/mir-opt/uninhabited_enum.rs b/tests/mir-opt/uninhabited_enum.rs index 90b5353f29143..124693c0fe2cc 100644 --- a/tests/mir-opt/uninhabited_enum.rs +++ b/tests/mir-opt/uninhabited_enum.rs @@ -1,4 +1,7 @@ // skip-filecheck +// +// check that we mark blocks with `!` locals as unreachable. +// (and currently don't do the same for other uninhabited types) #![feature(never_type)] #[derive(Copy, Clone)] diff --git a/tests/ui/coercion/coerce-loop-issue-122561.rs b/tests/ui/coercion/coerce-loop-issue-122561.rs index 50a2aacc91ad3..d79dfa28b0daf 100644 --- a/tests/ui/coercion/coerce-loop-issue-122561.rs +++ b/tests/ui/coercion/coerce-loop-issue-122561.rs @@ -1,4 +1,6 @@ -// Regression test for #122561 +// Regression test for . +// +// Tests suggestions for type mismatch of loop expressions. fn for_infinite() -> bool { for i in 0.. { diff --git a/tests/ui/coercion/coerce-loop-issue-122561.stderr b/tests/ui/coercion/coerce-loop-issue-122561.stderr index 6415fd554cba9..3fd6671565f18 100644 --- a/tests/ui/coercion/coerce-loop-issue-122561.stderr +++ b/tests/ui/coercion/coerce-loop-issue-122561.stderr @@ -1,5 +1,5 @@ warning: denote infinite loops with `loop { ... }` - --> $DIR/coerce-loop-issue-122561.rs:47:5 + --> $DIR/coerce-loop-issue-122561.rs:49:5 | LL | while true { | ^^^^^^^^^^ help: use `loop` @@ -7,13 +7,13 @@ LL | while true { = note: `#[warn(while_true)]` on by default warning: denote infinite loops with `loop { ... }` - --> $DIR/coerce-loop-issue-122561.rs:71:5 + --> $DIR/coerce-loop-issue-122561.rs:73:5 | LL | while true { | ^^^^^^^^^^ help: use `loop` error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:41:24 + --> $DIR/coerce-loop-issue-122561.rs:43:24 | LL | fn for_in_arg(a: &[(); for x in 0..2 {}]) -> bool { | ^^^^^^^^^^^^^^^^ expected `usize`, found `()` @@ -25,7 +25,7 @@ LL | fn for_in_arg(a: &[(); for x in 0..2 {} /* `usize` value */]) -> bool { | +++++++++++++++++++ error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:4:5 + --> $DIR/coerce-loop-issue-122561.rs:6:5 | LL | fn for_infinite() -> bool { | ---- expected `bool` because of return type @@ -43,7 +43,7 @@ LL + /* `bool` value */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:11:5 + --> $DIR/coerce-loop-issue-122561.rs:13:5 | LL | fn for_finite() -> String { | ------ expected `String` because of return type @@ -61,7 +61,7 @@ LL + /* `String` value */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:18:5 + --> $DIR/coerce-loop-issue-122561.rs:20:5 | LL | fn for_zero_times() -> bool { | ---- expected `bool` because of return type @@ -79,7 +79,7 @@ LL + /* `bool` value */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:25:5 + --> $DIR/coerce-loop-issue-122561.rs:27:5 | LL | fn for_never_type() -> ! { | - expected `!` because of return type @@ -98,7 +98,7 @@ LL + /* `loop {}` or `panic!("...")` */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:33:32 + --> $DIR/coerce-loop-issue-122561.rs:35:32 | LL | fn for_single_line() -> bool { for i in 0.. { return false; } } | ---- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `bool`, found `()` @@ -112,7 +112,7 @@ LL | fn for_single_line() -> bool { for i in 0.. { return false; } /* `bool` val | ++++++++++++++++++ error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:47:5 + --> $DIR/coerce-loop-issue-122561.rs:49:5 | LL | fn while_inifinite() -> bool { | ---- expected `bool` because of return type @@ -131,7 +131,7 @@ LL + /* `bool` value */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:56:5 + --> $DIR/coerce-loop-issue-122561.rs:58:5 | LL | fn while_finite() -> bool { | ---- expected `bool` because of return type @@ -151,7 +151,7 @@ LL + /* `bool` value */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:64:5 + --> $DIR/coerce-loop-issue-122561.rs:66:5 | LL | fn while_zero_times() -> bool { | ---- expected `bool` because of return type @@ -169,7 +169,7 @@ LL + /* `bool` value */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:71:5 + --> $DIR/coerce-loop-issue-122561.rs:73:5 | LL | fn while_never_type() -> ! { | - expected `!` because of return type @@ -188,7 +188,7 @@ LL + /* `loop {}` or `panic!("...")` */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:85:5 + --> $DIR/coerce-loop-issue-122561.rs:87:5 | LL | / for i in 0.. { LL | | @@ -203,7 +203,7 @@ LL + /* `i32` value */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:92:9 + --> $DIR/coerce-loop-issue-122561.rs:94:9 | LL | / for i in 0..5 { LL | | @@ -218,7 +218,7 @@ LL + /* `usize` value */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:98:9 + --> $DIR/coerce-loop-issue-122561.rs:100:9 | LL | / while false { LL | | @@ -233,7 +233,7 @@ LL + /* `usize` value */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:104:23 + --> $DIR/coerce-loop-issue-122561.rs:106:23 | LL | let _ = |a: &[(); for x in 0..2 {}]| {}; | ^^^^^^^^^^^^^^^^ expected `usize`, found `()` diff --git a/tests/ui/consts/let-irrefutable-pattern-ice-120337.rs b/tests/ui/consts/let-irrefutable-pattern-ice-120337.rs index e0d1d515deb02..24ff0a1f0719c 100644 --- a/tests/ui/consts/let-irrefutable-pattern-ice-120337.rs +++ b/tests/ui/consts/let-irrefutable-pattern-ice-120337.rs @@ -1,10 +1,18 @@ +// Regression test for . +// +// This checks that const eval doesn't cause an ICE when reading an uninhabited +// variant. (N.B. this is UB, but not currently detected by rustc) +// //@ check-pass #![feature(never_type)] + #[derive(Copy, Clone)] pub enum E { A(!), } + pub union U { u: (), e: E, } + pub const C: () = { - let E::A(ref a) = unsafe { &(&U { u: () }).e}; + let E::A(ref a) = unsafe { &(&U { u: () }).e }; }; fn main() {} From fd50a37726ca53a587efbe1e3137a8080c41831c Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 13 Nov 2025 23:26:41 +0100 Subject: [PATCH 24/24] move `DispatchFromDyn` test out of `never_type/` ??? --- tests/ui/{never_type => self}/dispatch_from_dyn_zst.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/ui/{never_type => self}/dispatch_from_dyn_zst.rs (100%) diff --git a/tests/ui/never_type/dispatch_from_dyn_zst.rs b/tests/ui/self/dispatch_from_dyn_zst.rs similarity index 100% rename from tests/ui/never_type/dispatch_from_dyn_zst.rs rename to tests/ui/self/dispatch_from_dyn_zst.rs