From d7fa6e527fef7337dc25b1e824664dd5847ba4a2 Mon Sep 17 00:00:00 2001 From: sgasho Date: Sun, 4 Jan 2026 22:56:17 +0900 Subject: [PATCH 1/8] enrich error info when tries to dlopen Enzyme --- compiler/rustc_codegen_llvm/messages.ftl | 3 ++- compiler/rustc_codegen_llvm/src/errors.rs | 5 ++++- compiler/rustc_codegen_llvm/src/lib.rs | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_codegen_llvm/messages.ftl b/compiler/rustc_codegen_llvm/messages.ftl index a637ae8184b4f..018d240b2ae50 100644 --- a/compiler/rustc_codegen_llvm/messages.ftl +++ b/compiler/rustc_codegen_llvm/messages.ftl @@ -1,4 +1,5 @@ -codegen_llvm_autodiff_component_unavailable = failed to load our autodiff backend. Did you install it via rustup? +codegen_llvm_autodiff_component_unavailable = failed to load our autodiff backend. + .note = load error: {$err} codegen_llvm_autodiff_without_enable = using the autodiff feature requires -Z autodiff=Enable codegen_llvm_autodiff_without_lto = using the autodiff feature requires setting `lto="fat"` in your Cargo.toml diff --git a/compiler/rustc_codegen_llvm/src/errors.rs b/compiler/rustc_codegen_llvm/src/errors.rs index c73140e041b60..439664b8b9a2b 100644 --- a/compiler/rustc_codegen_llvm/src/errors.rs +++ b/compiler/rustc_codegen_llvm/src/errors.rs @@ -34,7 +34,10 @@ impl Diagnostic<'_, G> for ParseTargetMachineConfig<'_> { #[derive(Diagnostic)] #[diag(codegen_llvm_autodiff_component_unavailable)] -pub(crate) struct AutoDiffComponentUnavailable; +#[note] +pub(crate) struct AutoDiffComponentUnavailable { + pub err: String, +} #[derive(Diagnostic)] #[diag(codegen_llvm_autodiff_without_lto)] diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 0952747449932..801d23342973f 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -249,8 +249,10 @@ impl CodegenBackend for LlvmCodegenBackend { use crate::back::lto::enable_autodiff_settings; if sess.opts.unstable_opts.autodiff.contains(&AutoDiff::Enable) { - if let Err(_) = llvm::EnzymeWrapper::get_or_init(&sess.opts.sysroot) { - sess.dcx().emit_fatal(crate::errors::AutoDiffComponentUnavailable); + if let Err(err) = llvm::EnzymeWrapper::get_or_init(&sess.opts.sysroot) { + sess.dcx().emit_fatal(crate::errors::AutoDiffComponentUnavailable { + err: format!("{err:?}"), + }); } enable_autodiff_settings(&sess.opts.unstable_opts.autodiff); } From b1d3d3af0115976bdb1cbf890fc4af85c61ab31f Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 5 Jan 2026 19:30:41 +0100 Subject: [PATCH 2/8] make `MarkdownItemInfo` a field struct --- src/librustdoc/html/markdown.rs | 17 ++++++++++++----- src/librustdoc/html/markdown/tests.rs | 2 +- src/librustdoc/html/render/mod.rs | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index a4d377432c914..834c0cb669c0e 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -111,7 +111,10 @@ pub(crate) struct MarkdownWithToc<'a> { } /// A tuple struct like `Markdown` that renders the markdown escaping HTML tags /// and includes no paragraph tags. -pub(crate) struct MarkdownItemInfo<'a>(pub(crate) &'a str, pub(crate) &'a mut IdMap); +pub(crate) struct MarkdownItemInfo<'a> { + pub(crate) content: &'a str, + pub(crate) ids: &'a mut IdMap, +} /// A tuple struct like `Markdown` that renders only the first paragraph. pub(crate) struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [RenderedLink]); @@ -1459,15 +1462,19 @@ impl MarkdownWithToc<'_> { } } -impl MarkdownItemInfo<'_> { +impl<'a> MarkdownItemInfo<'a> { + pub(crate) fn new(content: &'a str, ids: &'a mut IdMap) -> Self { + Self { content, ids } + } + pub(crate) fn write_into(self, mut f: impl fmt::Write) -> fmt::Result { - let MarkdownItemInfo(md, ids) = self; + let MarkdownItemInfo { content, ids } = self; // This is actually common enough to special-case - if md.is_empty() { + if content.is_empty() { return Ok(()); } - let p = Parser::new_ext(md, main_body_opts()).into_offset_iter(); + let p = Parser::new_ext(content, main_body_opts()).into_offset_iter(); // Treat inline HTML as plain text. let p = p.map(|event| match event.0 { diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs index 61fd428746332..14d86a8abf57a 100644 --- a/src/librustdoc/html/markdown/tests.rs +++ b/src/librustdoc/html/markdown/tests.rs @@ -471,7 +471,7 @@ fn test_markdown_html_escape() { fn t(input: &str, expect: &str) { let mut idmap = IdMap::new(); let mut output = String::new(); - MarkdownItemInfo(input, &mut idmap).write_into(&mut output).unwrap(); + MarkdownItemInfo::new(input, &mut idmap).write_into(&mut output).unwrap(); assert_eq!(output, expect, "original: {}", input); } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 4529f5a8c0163..de5c8a7652759 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -877,7 +877,7 @@ fn short_item_info( if let Some(note) = note { let note = note.as_str(); let mut id_map = cx.id_map.borrow_mut(); - let html = MarkdownItemInfo(note, &mut id_map); + let html = MarkdownItemInfo::new(note, &mut id_map); message.push_str(": "); html.write_into(&mut message).unwrap(); } From 14ac6a1d3a3e153a546d3e71b51866ca77a02dcd Mon Sep 17 00:00:00 2001 From: sgasho Date: Wed, 7 Jan 2026 00:19:09 +0900 Subject: [PATCH 3/8] Modified to output error messages appropriate to the situation --- compiler/rustc_codegen_llvm/messages.ftl | 6 ++-- compiler/rustc_codegen_llvm/src/errors.rs | 8 ++++- compiler/rustc_codegen_llvm/src/lib.rs | 12 ++++--- .../rustc_codegen_llvm/src/llvm/enzyme_ffi.rs | 34 ++++++++++++++----- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_codegen_llvm/messages.ftl b/compiler/rustc_codegen_llvm/messages.ftl index 018d240b2ae50..85cb7499cca4e 100644 --- a/compiler/rustc_codegen_llvm/messages.ftl +++ b/compiler/rustc_codegen_llvm/messages.ftl @@ -1,5 +1,7 @@ -codegen_llvm_autodiff_component_unavailable = failed to load our autodiff backend. - .note = load error: {$err} +codegen_llvm_autodiff_component_missing = autodiff backend not found in the sysroot: {$err} + .note = it will be distributed via rustup in the future + +codegen_llvm_autodiff_component_unavailable = failed to load our autodiff backend: {$err} codegen_llvm_autodiff_without_enable = using the autodiff feature requires -Z autodiff=Enable codegen_llvm_autodiff_without_lto = using the autodiff feature requires setting `lto="fat"` in your Cargo.toml diff --git a/compiler/rustc_codegen_llvm/src/errors.rs b/compiler/rustc_codegen_llvm/src/errors.rs index 439664b8b9a2b..bd42cf5569664 100644 --- a/compiler/rustc_codegen_llvm/src/errors.rs +++ b/compiler/rustc_codegen_llvm/src/errors.rs @@ -34,11 +34,17 @@ impl Diagnostic<'_, G> for ParseTargetMachineConfig<'_> { #[derive(Diagnostic)] #[diag(codegen_llvm_autodiff_component_unavailable)] -#[note] pub(crate) struct AutoDiffComponentUnavailable { pub err: String, } +#[derive(Diagnostic)] +#[diag(codegen_llvm_autodiff_component_missing)] +#[note] +pub(crate) struct AutoDiffComponentMissing { + pub err: String, +} + #[derive(Diagnostic)] #[diag(codegen_llvm_autodiff_without_lto)] pub(crate) struct AutoDiffWithoutLto; diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 801d23342973f..438a74e0a0912 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -249,10 +249,14 @@ impl CodegenBackend for LlvmCodegenBackend { use crate::back::lto::enable_autodiff_settings; if sess.opts.unstable_opts.autodiff.contains(&AutoDiff::Enable) { - if let Err(err) = llvm::EnzymeWrapper::get_or_init(&sess.opts.sysroot) { - sess.dcx().emit_fatal(crate::errors::AutoDiffComponentUnavailable { - err: format!("{err:?}"), - }); + match llvm::EnzymeWrapper::get_or_init(&sess.opts.sysroot) { + Ok(_) => {} + Err(llvm::EnzymeLibraryError::NotFound { err }) => { + sess.dcx().emit_fatal(crate::errors::AutoDiffComponentMissing { err }); + } + Err(llvm::EnzymeLibraryError::LoadFailed { err }) => { + sess.dcx().emit_fatal(crate::errors::AutoDiffComponentUnavailable { err }); + } } enable_autodiff_settings(&sess.opts.unstable_opts.autodiff); } diff --git a/compiler/rustc_codegen_llvm/src/llvm/enzyme_ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/enzyme_ffi.rs index b11310b970d03..67fbc0f53adc9 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/enzyme_ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/enzyme_ffi.rs @@ -153,7 +153,7 @@ pub(crate) mod Enzyme_AD { fn load_ptr_by_symbol_mut_void( lib: &libloading::Library, bytes: &[u8], - ) -> Result<*mut c_void, Box> { + ) -> Result<*mut c_void, libloading::Error> { unsafe { let s: libloading::Symbol<'_, *mut c_void> = lib.get(bytes)?; // libloading = 0.9.0: try_as_raw_ptr always succeeds and returns Some @@ -192,15 +192,27 @@ pub(crate) mod Enzyme_AD { static ENZYME_INSTANCE: OnceLock> = OnceLock::new(); + #[derive(Debug)] + pub(crate) enum EnzymeLibraryError { + NotFound { err: String }, + LoadFailed { err: String }, + } + + impl From for EnzymeLibraryError { + fn from(err: libloading::Error) -> Self { + Self::LoadFailed { err: format!("{err:?}") } + } + } + impl EnzymeWrapper { /// Initialize EnzymeWrapper with the given sysroot if not already initialized. /// Safe to call multiple times - subsequent calls are no-ops due to OnceLock. pub(crate) fn get_or_init( sysroot: &rustc_session::config::Sysroot, - ) -> Result, Box> { + ) -> Result, EnzymeLibraryError> { let mtx: &'static Mutex = ENZYME_INSTANCE.get_or_try_init(|| { let w = Self::call_dynamic(sysroot)?; - Ok::<_, Box>(Mutex::new(w)) + Ok::<_, EnzymeLibraryError>(Mutex::new(w)) })?; Ok(mtx.lock().unwrap()) @@ -351,7 +363,7 @@ pub(crate) mod Enzyme_AD { #[allow(non_snake_case)] fn call_dynamic( sysroot: &rustc_session::config::Sysroot, - ) -> Result> { + ) -> Result { let enzyme_path = Self::get_enzyme_path(sysroot)?; let lib = unsafe { libloading::Library::new(enzyme_path)? }; @@ -416,7 +428,7 @@ pub(crate) mod Enzyme_AD { }) } - fn get_enzyme_path(sysroot: &Sysroot) -> Result { + fn get_enzyme_path(sysroot: &Sysroot) -> Result { let llvm_version_major = unsafe { LLVMRustVersionMajor() }; let path_buf = sysroot @@ -434,15 +446,19 @@ pub(crate) mod Enzyme_AD { .map(|p| p.join("lib").display().to_string()) .collect::>() .join("\n* "); - format!( - "failed to find a `libEnzyme-{llvm_version_major}` folder \ + EnzymeLibraryError::NotFound { + err: format!( + "failed to find a `libEnzyme-{llvm_version_major}` folder \ in the sysroot candidates:\n* {candidates}" - ) + ), + } })?; Ok(path_buf .to_str() - .ok_or_else(|| format!("invalid UTF-8 in path: {}", path_buf.display()))? + .ok_or_else(|| EnzymeLibraryError::LoadFailed { + err: format!("invalid UTF-8 in path: {}", path_buf.display()), + })? .to_string()) } } From c01b87ba81d522c3a3c1cf4bd17a43a7ffb47818 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 6 Jan 2026 12:04:38 +0100 Subject: [PATCH 4/8] render intra-doc links in the `#[deprectated]` note --- compiler/rustc_ast/src/attr/mod.rs | 34 ++++++++++++++ compiler/rustc_hir/src/hir.rs | 8 ++++ compiler/rustc_resolve/src/rustdoc.rs | 13 +++++- src/librustdoc/clean/types.rs | 10 +++++ src/librustdoc/html/markdown.rs | 21 ++++++--- src/librustdoc/html/markdown/tests.rs | 2 +- src/librustdoc/html/render/mod.rs | 3 +- .../passes/collect_intra_doc_links.rs | 45 +++++++++++++------ tests/rustdoc-ui/intra-doc/deprecated.rs | 10 +++++ tests/rustdoc-ui/intra-doc/deprecated.stderr | 43 ++++++++++++++++++ tests/rustdoc/intra-doc/deprecated.rs | 12 +++++ 11 files changed, 179 insertions(+), 22 deletions(-) create mode 100644 tests/rustdoc-ui/intra-doc/deprecated.rs create mode 100644 tests/rustdoc-ui/intra-doc/deprecated.stderr create mode 100644 tests/rustdoc/intra-doc/deprecated.rs diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index c53188a22aedd..f63f00420ce48 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -213,6 +213,34 @@ impl AttributeExt for Attribute { } } + fn deprecation_note(&self) -> Option { + match &self.kind { + AttrKind::Normal(normal) if normal.item.path == sym::deprecated => { + let meta = &normal.item; + + // #[deprecated = "..."] + if let Some(s) = meta.value_str() { + return Some(s); + } + + // #[deprecated(note = "...")] + if let Some(list) = meta.meta_item_list() { + for nested in list { + if let Some(mi) = nested.meta_item() + && mi.path == sym::note + && let Some(s) = mi.value_str() + { + return Some(s); + } + } + } + + None + } + _ => None, + } + } + fn doc_resolution_scope(&self) -> Option { match &self.kind { AttrKind::DocComment(..) => Some(self.style), @@ -255,6 +283,7 @@ impl Attribute { pub fn may_have_doc_links(&self) -> bool { self.doc_str().is_some_and(|s| comments::may_have_doc_links(s.as_str())) + || self.deprecation_note().is_some_and(|s| comments::may_have_doc_links(s.as_str())) } /// Extracts the MetaItem from inside this Attribute. @@ -850,6 +879,11 @@ pub trait AttributeExt: Debug { /// * `#[doc(...)]` returns `None`. fn doc_str(&self) -> Option; + /// Returns the deprecation note if this is deprecation attribute. + /// * `#[deprecated = "note"]` returns `Some("note")`. + /// * `#[deprecated(note = "note", ...)]` returns `Some("note")`. + fn deprecation_note(&self) -> Option; + fn is_proc_macro_attr(&self) -> bool { [sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive] .iter() diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index da11d1bd64b02..e903503b71d30 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1397,6 +1397,14 @@ impl AttributeExt for Attribute { } } + #[inline] + fn deprecation_note(&self) -> Option { + match &self { + Attribute::Parsed(AttributeKind::Deprecation { deprecation, .. }) => deprecation.note, + _ => None, + } + } + fn is_automatically_derived_attr(&self) -> bool { matches!(self, Attribute::Parsed(AttributeKind::AutomaticallyDerived(..))) } diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 7f7c423acb40a..9f74a7801d2ee 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -410,8 +410,17 @@ pub fn may_be_doc_link(link_type: LinkType) -> bool { /// Simplified version of `preprocessed_markdown_links` from rustdoc. /// Must return at least the same links as it, but may add some more links on top of that. pub(crate) fn attrs_to_preprocessed_links(attrs: &[A]) -> Vec> { - let (doc_fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true); - let doc = prepare_to_doc_link_resolution(&doc_fragments).into_values().next().unwrap(); + let (doc_fragments, other_attrs) = + attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), false); + let mut doc = + prepare_to_doc_link_resolution(&doc_fragments).into_values().next().unwrap_or_default(); + + for attr in other_attrs { + if let Some(note) = attr.deprecation_note() { + doc += note.as_str(); + doc += "\n"; + } + } parse_links(&doc) } diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index a390a03ff1144..c3bafd3db13ac 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -7,6 +7,7 @@ use std::{fmt, iter}; use arrayvec::ArrayVec; use itertools::Either; use rustc_abi::{ExternAbi, VariantIdx}; +use rustc_ast::attr::AttributeExt; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::thin_vec::ThinVec; use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation, DocAttribute}; @@ -450,7 +451,16 @@ impl Item { } pub(crate) fn attr_span(&self, tcx: TyCtxt<'_>) -> rustc_span::Span { + let deprecation_notes = self + .attrs + .other_attrs + .iter() + .filter_map(|attr| attr.deprecation_note().map(|_| attr.span())); + span_of_fragments(&self.attrs.doc_strings) + .into_iter() + .chain(deprecation_notes) + .reduce(|a, b| a.to(b)) .unwrap_or_else(|| self.span(tcx).map_or(DUMMY_SP, |span| span.inner())) } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 834c0cb669c0e..c472c20a7dc71 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -113,6 +113,7 @@ pub(crate) struct MarkdownWithToc<'a> { /// and includes no paragraph tags. pub(crate) struct MarkdownItemInfo<'a> { pub(crate) content: &'a str, + pub(crate) links: &'a [RenderedLink], pub(crate) ids: &'a mut IdMap, } /// A tuple struct like `Markdown` that renders only the first paragraph. @@ -1463,18 +1464,27 @@ impl MarkdownWithToc<'_> { } impl<'a> MarkdownItemInfo<'a> { - pub(crate) fn new(content: &'a str, ids: &'a mut IdMap) -> Self { - Self { content, ids } + pub(crate) fn new(content: &'a str, links: &'a [RenderedLink], ids: &'a mut IdMap) -> Self { + Self { content, links, ids } } pub(crate) fn write_into(self, mut f: impl fmt::Write) -> fmt::Result { - let MarkdownItemInfo { content, ids } = self; + let MarkdownItemInfo { content: md, links, ids } = self; // This is actually common enough to special-case - if content.is_empty() { + if md.is_empty() { return Ok(()); } - let p = Parser::new_ext(content, main_body_opts()).into_offset_iter(); + + let replacer = move |broken_link: BrokenLink<'_>| { + links + .iter() + .find(|link| *link.original_text == *broken_link.reference) + .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) + }; + + let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(replacer)); + let p = p.into_offset_iter(); // Treat inline HTML as plain text. let p = p.map(|event| match event.0 { @@ -1484,6 +1494,7 @@ impl<'a> MarkdownItemInfo<'a> { ids.handle_footnotes(|ids, existing_footnotes| { let p = HeadingLinks::new(p, None, ids, HeadingOffset::H1); + let p = SpannedLinkReplacer::new(p, links); let p = footnotes::Footnotes::new(p, existing_footnotes); let p = TableWrapper::new(p.map(|(ev, _)| ev)); let p = p.filter(|event| { diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs index 14d86a8abf57a..1c99ccc5228b1 100644 --- a/src/librustdoc/html/markdown/tests.rs +++ b/src/librustdoc/html/markdown/tests.rs @@ -471,7 +471,7 @@ fn test_markdown_html_escape() { fn t(input: &str, expect: &str) { let mut idmap = IdMap::new(); let mut output = String::new(); - MarkdownItemInfo::new(input, &mut idmap).write_into(&mut output).unwrap(); + MarkdownItemInfo::new(input, &[], &mut idmap).write_into(&mut output).unwrap(); assert_eq!(output, expect, "original: {}", input); } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index de5c8a7652759..63de870f07f45 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -877,7 +877,8 @@ fn short_item_info( if let Some(note) = note { let note = note.as_str(); let mut id_map = cx.id_map.borrow_mut(); - let html = MarkdownItemInfo::new(note, &mut id_map); + let links = item.links(cx); + let html = MarkdownItemInfo::new(note, &links, &mut id_map); message.push_str(": "); html.write_into(&mut message).unwrap(); } diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 3abf0fee3959a..07d6efaa97e15 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -7,6 +7,7 @@ use std::fmt::Display; use std::mem; use std::ops::Range; +use rustc_ast::attr::AttributeExt; use rustc_ast::util::comments::may_have_doc_links; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::intern::Interned; @@ -1047,18 +1048,7 @@ impl LinkCollector<'_, '_> { return; } - // We want to resolve in the lexical scope of the documentation. - // In the presence of re-exports, this is not the same as the module of the item. - // Rather than merging all documentation into one, resolve it one attribute at a time - // so we know which module it came from. - for (item_id, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings) { - if !may_have_doc_links(&doc) { - continue; - } - debug!("combined_docs={doc}"); - // NOTE: if there are links that start in one crate and end in another, this will not resolve them. - // This is a degenerate case and it's not supported by rustdoc. - let item_id = item_id.unwrap_or_else(|| item.item_id.expect_def_id()); + let mut insert_links = |item_id, doc: &str| { let module_id = match self.cx.tcx.def_kind(item_id) { DefKind::Mod if item.inner_docs(self.cx.tcx) => item_id, _ => find_nearest_parent_module(self.cx.tcx, item_id).unwrap(), @@ -1074,6 +1064,35 @@ impl LinkCollector<'_, '_> { .insert(link); } } + }; + + // We want to resolve in the lexical scope of the documentation. + // In the presence of re-exports, this is not the same as the module of the item. + // Rather than merging all documentation into one, resolve it one attribute at a time + // so we know which module it came from. + for (item_id, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings) { + if !may_have_doc_links(&doc) { + continue; + } + + debug!("combined_docs={doc}"); + // NOTE: if there are links that start in one crate and end in another, this will not resolve them. + // This is a degenerate case and it's not supported by rustdoc. + let item_id = item_id.unwrap_or_else(|| item.item_id.expect_def_id()); + insert_links(item_id, &doc) + } + + // Also resolve links in the note text of `#[deprecated]`. + for attr in &item.attrs.other_attrs { + let Some(note_sym) = attr.deprecation_note() else { continue }; + let note = note_sym.as_str(); + + if !may_have_doc_links(note) { + continue; + } + + debug!("deprecated_note={note}"); + insert_links(item.item_id.expect_def_id(), note) } } @@ -1086,7 +1105,7 @@ impl LinkCollector<'_, '_> { /// FIXME(jynelson): this is way too many arguments fn resolve_link( &mut self, - dox: &String, + dox: &str, item: &Item, item_id: DefId, module_id: DefId, diff --git a/tests/rustdoc-ui/intra-doc/deprecated.rs b/tests/rustdoc-ui/intra-doc/deprecated.rs new file mode 100644 index 0000000000000..37c27dcde598a --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/deprecated.rs @@ -0,0 +1,10 @@ +#![deny(rustdoc::broken_intra_doc_links)] + +#[deprecated = "[broken cross-reference](TypeAlias::hoge)"] //~ ERROR +pub struct A; + +#[deprecated(since = "0.0.0", note = "[broken cross-reference](TypeAlias::hoge)")] //~ ERROR +pub struct B1; + +#[deprecated(note = "[broken cross-reference](TypeAlias::hoge)", since = "0.0.0")] //~ ERROR +pub struct B2; diff --git a/tests/rustdoc-ui/intra-doc/deprecated.stderr b/tests/rustdoc-ui/intra-doc/deprecated.stderr new file mode 100644 index 0000000000000..9bd64544eef82 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/deprecated.stderr @@ -0,0 +1,43 @@ +error: unresolved link to `TypeAlias::hoge` + --> $DIR/deprecated.rs:3:1 + | +LL | #[deprecated = "[broken cross-reference](TypeAlias::hoge)"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the link appears in this line: + + [broken cross-reference](TypeAlias::hoge) + ^^^^^^^^^^^^^^^ + = note: no item named `TypeAlias` in scope +note: the lint level is defined here + --> $DIR/deprecated.rs:1:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unresolved link to `TypeAlias::hoge` + --> $DIR/deprecated.rs:6:1 + | +LL | #[deprecated(since = "0.0.0", note = "[broken cross-reference](TypeAlias::hoge)")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the link appears in this line: + + [broken cross-reference](TypeAlias::hoge) + ^^^^^^^^^^^^^^^ + = note: no item named `TypeAlias` in scope + +error: unresolved link to `TypeAlias::hoge` + --> $DIR/deprecated.rs:9:1 + | +LL | #[deprecated(note = "[broken cross-reference](TypeAlias::hoge)", since = "0.0.0")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the link appears in this line: + + [broken cross-reference](TypeAlias::hoge) + ^^^^^^^^^^^^^^^ + = note: no item named `TypeAlias` in scope + +error: aborting due to 3 previous errors + diff --git a/tests/rustdoc/intra-doc/deprecated.rs b/tests/rustdoc/intra-doc/deprecated.rs new file mode 100644 index 0000000000000..6f8639593a2d4 --- /dev/null +++ b/tests/rustdoc/intra-doc/deprecated.rs @@ -0,0 +1,12 @@ +//@ has deprecated/struct.A.html '//a[@href="{{channel}}/core/ops/range/struct.Range.html#structfield.start"]' 'start' +//@ has deprecated/struct.B1.html '//a[@href="{{channel}}/std/io/error/enum.ErrorKind.html#variant.NotFound"]' 'not_found' +//@ has deprecated/struct.B2.html '//a[@href="{{channel}}/std/io/error/enum.ErrorKind.html#variant.NotFound"]' 'not_found' + +#[deprecated = "[start][std::ops::Range::start]"] +pub struct A; + +#[deprecated(since = "0.0.0", note = "[not_found][std::io::ErrorKind::NotFound]")] +pub struct B1; + +#[deprecated(note = "[not_found][std::io::ErrorKind::NotFound]", since = "0.0.0")] +pub struct B2; From 11324546168855ddb221b019e0fa004e9de78881 Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Wed, 7 Jan 2026 06:03:51 +0100 Subject: [PATCH 5/8] tests/ui/runtime/on-broken-pipe/with-rustc_main.rs: Not needed so remove It was added in ddee45e1d7fd when SIGPIPE was controlled with an attribute on `fn main()` which meant it could also be combined with `#[rustc_main]`: #[unix_sigpipe = "sig_dfl"] #[rustc_main] fn rustc_main() { It stopped being needed cde0cde151f3 when `#[unix_sigpipe = "..."]` was replaced by `-Zon-broken-pipe=...`. And it will not be needed when `-Zon-broken-pipe=...` is replaced by an Externally Implementable Item. Let's remove this test. --- .../ui/runtime/on-broken-pipe/with-rustc_main.rs | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 tests/ui/runtime/on-broken-pipe/with-rustc_main.rs diff --git a/tests/ui/runtime/on-broken-pipe/with-rustc_main.rs b/tests/ui/runtime/on-broken-pipe/with-rustc_main.rs deleted file mode 100644 index c40590ad87f47..0000000000000 --- a/tests/ui/runtime/on-broken-pipe/with-rustc_main.rs +++ /dev/null @@ -1,15 +0,0 @@ -//@ run-pass -//@ aux-build:sigpipe-utils.rs -//@ compile-flags: -Zon-broken-pipe=kill -//@ only-unix because SIGPIPE is a unix thing - -#![feature(rustc_attrs)] - -#[rustc_main] -fn rustc_main() { - extern crate sigpipe_utils; - - // `-Zon-broken-pipe=kill` is active, so SIGPIPE handler shall be - // SIG_DFL. Note that we have a #[rustc_main], but it should still work. - sigpipe_utils::assert_sigpipe_handler(sigpipe_utils::SignalHandler::Default); -} From 43e1604defa289b21cacf5f8e3c7d1ae741ecdca Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 7 Jan 2026 14:51:18 +0200 Subject: [PATCH 6/8] rustc book: fix grammar --- src/doc/rustc/src/remap-source-paths.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc/src/remap-source-paths.md b/src/doc/rustc/src/remap-source-paths.md index ebe92d71158a4..a499f9d3c77e5 100644 --- a/src/doc/rustc/src/remap-source-paths.md +++ b/src/doc/rustc/src/remap-source-paths.md @@ -41,7 +41,7 @@ This example replaces all occurrences of `/home/user/project` in emitted paths w ## Caveats and Limitations -### Linkers generated paths +### Paths generated by linkers On some platforms like `x86_64-pc-windows-msvc`, the linker may embed absolute host paths and compiler arguments into debug info files (like `.pdb`) independently of `rustc`. From 5b4dbe02131e184a21cffea58f4550b1787edf9b Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 7 Jan 2026 14:53:21 +0200 Subject: [PATCH 7/8] add missing commas --- src/doc/rustc/src/remap-source-paths.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc/src/remap-source-paths.md b/src/doc/rustc/src/remap-source-paths.md index a499f9d3c77e5..dc00278fac676 100644 --- a/src/doc/rustc/src/remap-source-paths.md +++ b/src/doc/rustc/src/remap-source-paths.md @@ -3,7 +3,7 @@ `rustc` supports remapping source paths prefixes **as a best effort** in all compiler generated output, including compiler diagnostics, debugging information, macro expansions, etc. -This is useful for normalizing build products, for example by removing the current directory +This is useful for normalizing build products, for example, by removing the current directory out of the paths emitted into object files. The remapping is done via the `--remap-path-prefix` option. @@ -54,7 +54,7 @@ The `--remap-path-prefix` option does not affect these linker-generated paths. ### Textual replacement only The remapping is strictly textual and does not account for different path separator conventions across -platforms. Care must be taken when specifying prefixes, especially on Windows where both `/` and `\` may +platforms. Care must be taken when specifying prefixes, especially on Windows, where both `/` and `\` may appear in paths. ### External tools From 870626a390f267fc1a21e0712b6639bde3091c71 Mon Sep 17 00:00:00 2001 From: Immad Mir Date: Wed, 7 Jan 2026 23:10:44 +0530 Subject: [PATCH 8/8] Move issue-12660 to 'ui/cross-crate/ with a descriptive name Author: Immad Mir --- .../issue-12660-aux.rs => cross-crate/auxiliary/aux-12660.rs} | 0 .../cross-crate-unit-struct-reexport.rs} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/ui/{issues/auxiliary/issue-12660-aux.rs => cross-crate/auxiliary/aux-12660.rs} (100%) rename tests/ui/{issues/issue-12660.rs => cross-crate/cross-crate-unit-struct-reexport.rs} (82%) diff --git a/tests/ui/issues/auxiliary/issue-12660-aux.rs b/tests/ui/cross-crate/auxiliary/aux-12660.rs similarity index 100% rename from tests/ui/issues/auxiliary/issue-12660-aux.rs rename to tests/ui/cross-crate/auxiliary/aux-12660.rs diff --git a/tests/ui/issues/issue-12660.rs b/tests/ui/cross-crate/cross-crate-unit-struct-reexport.rs similarity index 82% rename from tests/ui/issues/issue-12660.rs rename to tests/ui/cross-crate/cross-crate-unit-struct-reexport.rs index 3aa3426519afc..2d4e0c654ef39 100644 --- a/tests/ui/issues/issue-12660.rs +++ b/tests/ui/cross-crate/cross-crate-unit-struct-reexport.rs @@ -1,5 +1,5 @@ //@ run-pass -//@ aux-build:issue-12660-aux.rs +//@ aux-build:aux-12660.rs extern crate issue12660aux;