Rollup merge of #151120 - ice-deprecated-note-on-reexport, r=lolbinarycat

Fix deprecated attribute intra-doc link not resolved in the right location on reexported item

Fixes https://github.com/rust-lang/rust/issues/151028.
Follow-up of https://github.com/rust-lang/rust/pull/150721.

So when we resolve an intra-doc link, its current context (the module from which we resolve in short) is very important. However, when we use an intra-doc link in a `#[deprecated]` attribute on a reexported item, we were using the context of the reexported item and not of the `use` itself. Meaning that if you have an intra-doc link `std::mem::drop` on an item from another crate (let's say `core`), then the import will simply fail since there is no `std` dependency.

Now comes the not so funny fix: at this point, we don't know anymore where the attribute came from (ie, from the reexport or from the reexported item) since we already merged the attribute at this point. The solution I found to go around this problem is to check if the item span contains the attribute, and if not, then we use the `inline_stmt_id` as context instead of the item's ID. I'm not super happy and I'm sure we'll find corner cases in the future (like with macros), however there are a few things that mitigate this fix:
1. The only way to generate an attribute with a macro with its item while having different spans is by using proc-macros. In that case, we can always default to the `inline_stmt_id` as context and be fine, but I guess we'll see when get there.
2. It only concerns reexports, so the area of the problem is quite restricted.

Hopefully this explanation made sense. :)

cc @folkertdev
r? @lolbinarycat
This commit is contained in:
Stuart Cook 2026-01-17 11:47:18 +11:00 committed by GitHub
commit 176bf87df0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 36 additions and 7 deletions

View file

@ -7,7 +7,6 @@ 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;
@ -1062,10 +1061,11 @@ fn preprocessed_markdown_links(s: &str) -> Vec<PreprocessedMarkdownLink> {
impl LinkCollector<'_, '_> {
#[instrument(level = "debug", skip_all)]
fn resolve_links(&mut self, item: &Item) {
let tcx = self.cx.tcx;
if !self.cx.document_private()
&& let Some(def_id) = item.item_id.as_def_id()
&& let Some(def_id) = def_id.as_local()
&& !self.cx.tcx.effective_visibilities(()).is_exported(def_id)
&& !tcx.effective_visibilities(()).is_exported(def_id)
&& !has_primitive_or_keyword_or_attribute_docs(&item.attrs.other_attrs)
{
// Skip link resolution for non-exported items.
@ -1073,9 +1073,9 @@ impl LinkCollector<'_, '_> {
}
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(),
let module_id = match tcx.def_kind(item_id) {
DefKind::Mod if item.inner_docs(tcx) => item_id,
_ => find_nearest_parent_module(tcx, item_id).unwrap(),
};
for md_link in preprocessed_markdown_links(&doc) {
let link = self.resolve_link(&doc, item, item_id, module_id, &md_link);
@ -1108,7 +1108,14 @@ impl LinkCollector<'_, '_> {
// 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 rustc_hir::Attribute::Parsed(rustc_hir::attrs::AttributeKind::Deprecation {
span,
deprecation,
}) = attr
else {
continue;
};
let Some(note_sym) = deprecation.note else { continue };
let note = note_sym.as_str();
if !may_have_doc_links(note) {
@ -1116,7 +1123,18 @@ impl LinkCollector<'_, '_> {
}
debug!("deprecated_note={note}");
insert_links(item.item_id.expect_def_id(), note)
// When resolving an intra-doc link inside a deprecation note that is on an inlined
// `use` statement, we need to use the `def_id` of the `use` statement, not the
// inlined item.
// <https://github.com/rust-lang/rust/pull/151120>
let item_id = if let Some(inline_stmt_id) = item.inline_stmt_id
&& item.span(tcx).is_none_or(|item_span| !item_span.inner().contains(*span))
{
inline_stmt_id.to_def_id()
} else {
item.item_id.expect_def_id()
};
insert_links(item_id, note)
}
}

View file

@ -0,0 +1,11 @@
// This test ensures that the intra-doc link in `deprecated` note is resolved at the correct
// location (ie in the current crate and not in the reexported item's location/crate) and
// therefore doesn't crash.
//
// This is a regression test for <https://github.com/rust-lang/rust/issues/151028>.
#![crate_name = "foo"]
#[deprecated(note = "use [`std::mem::forget`]")]
#[doc(inline)]
pub use std::mem::drop;