Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
963746f
Add new rustdoc `broken_footnote` lint
GuillaumeGomez Mar 11, 2025
e7b4bc8
Add ui test for rustdoc `broken_footnote` lint
GuillaumeGomez Mar 11, 2025
6b500aa
Add new `unused_footnote_definition` rustdoc lint
GuillaumeGomez Mar 11, 2025
621d984
Add ui test for new `unused_footnote_definition` rustdoc lint
GuillaumeGomez Mar 11, 2025
4ddd48c
Improve description of new rustdoc lints
GuillaumeGomez Jan 26, 2026
5deff4b
Remove outdated comment
GuillaumeGomez Feb 25, 2026
d33610f
Add extra "broken_footnote" lint ui test
GuillaumeGomez Mar 12, 2026
c2c6cb4
Fix too-short variance slice in `variances_of` cycle recovery
xmakro Jun 20, 2026
b973eb9
Allow unstable attribute on foreign types and add stability test
kantnero Jun 22, 2026
2592b41
Add test to confirm usage of unstable foreign type error
kantnero Jun 26, 2026
d69c87a
Upgrade `jsonsocck` and `jsondoclint` to edition 2024.
obi1kenobi Jun 27, 2026
738c160
Fix backslashes and line breaks in footnote lint
notriddle Jun 27, 2026
9ab5135
Upgrade `rustdoc-json-types` to 2024 edition.
obi1kenobi Jun 27, 2026
18c10cd
Adds RmetaLinkCache a per-link cache that uses path as the key of dec…
mehdiakiki Jun 20, 2026
94888f0
update bless output
kantnero Jun 27, 2026
cabfdf8
Rollup merge of #158194 - mehdiakiki:rlib-link-meta-cache, r=petroche…
JonathanBrouwer Jun 27, 2026
dcbfcf5
Rollup merge of #137858 - GuillaumeGomez:unused_footnote_def, r=notri…
JonathanBrouwer Jun 27, 2026
f76d5a0
Rollup merge of #158163 - xmakro:fix-variances-cycle-recovery-count, …
JonathanBrouwer Jun 27, 2026
1b0c5c8
Rollup merge of #158233 - kantnero:allow-unstable-attr-on-target, r=J…
JonathanBrouwer Jun 27, 2026
926d8ad
Rollup merge of #158470 - obi1kenobi:pg/upgrade-json-tool-editions, r…
JonathanBrouwer Jun 27, 2026
bc0899f
Rollup merge of #158488 - obi1kenobi:pg/upgrade-rustdoc-json-types-ed…
JonathanBrouwer Jun 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/attributes/stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[
Allow(Target::Static),
Allow(Target::ForeignFn),
Allow(Target::ForeignStatic),
Allow(Target::ForeignTy),
Allow(Target::ExternCrate),
]);

Expand Down
26 changes: 18 additions & 8 deletions compiler/rustc_codegen_ssa/src/back/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use rustc_target::spec::Arch;
use tracing::trace;

use super::metadata::{create_compressed_metadata_file, search_for_section};
use super::rmeta_link;
use super::rmeta_link::{self, RmetaLinkCache};
use super::symbol_edit::{apply_edits, collect_internal_names};
use crate::common;
// Public for ArchiveBuilderBuilder::extract_bundled_libs
Expand Down Expand Up @@ -311,7 +311,7 @@ fn find_binutils_dlltool(sess: &Session) -> OsString {
}

pub enum AddArchiveKind<'a> {
Rlib(/*skip*/ &'a dyn Fn(&str, ArchiveEntryKind) -> bool),
Rlib(&'a mut RmetaLinkCache, /*skip*/ &'a dyn Fn(&str, ArchiveEntryKind) -> bool),
Other,
}

Expand Down Expand Up @@ -466,7 +466,11 @@ pub fn try_extract_macho_fat_archive(
}

impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
fn add_archive(&mut self, archive_path: &Path, ar_kind: AddArchiveKind<'_>) -> io::Result<()> {
fn add_archive(
&mut self,
archive_path: &Path,
mut ar_kind: AddArchiveKind<'_>,
) -> io::Result<()> {
let mut archive_path = archive_path.to_path_buf();
if self.sess.target.llvm_target.contains("-apple-macosx")
&& let Some(new_archive_path) = try_extract_macho_fat_archive(self.sess, &archive_path)?
Expand All @@ -481,8 +485,14 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
let archive_map = unsafe { Mmap::map(File::open(&archive_path)?)? };
let archive = ArchiveFile::parse(&*archive_map)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
let metadata_link = match ar_kind {
AddArchiveKind::Rlib(..) => rmeta_link::read(&archive, &archive_map, &archive_path),
let skip = match &ar_kind {
AddArchiveKind::Rlib(_, skip) => Some(*skip),
AddArchiveKind::Other => None,
};
let metadata_link = match &mut ar_kind {
AddArchiveKind::Rlib(cache, _) => cache.get_or_insert_with(&archive_path, || {
rmeta_link::read(&archive, &archive_map, &archive_path)
}),
AddArchiveKind::Other => None,
};
let archive_index = self.src_archives.len();
Expand Down Expand Up @@ -512,9 +522,9 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
} else {
ArchiveEntryKind::Other
};
let drop = match ar_kind {
AddArchiveKind::Rlib(skip) => skip(&file_name, kind),
AddArchiveKind::Other => false,
let drop = match skip {
Some(skip) => skip(&file_name, kind),
None => false,
};
if !drop {
let source = if entry.is_thin() {
Expand Down
16 changes: 14 additions & 2 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ use super::archive::{
use super::command::Command;
use super::linker::{self, Linker};
use super::metadata::{MetadataPosition, create_wrapper_file};
use super::rmeta_link::RmetaLinkCache;
use super::rpath::{self, RPathConfig};
use super::{apple, rmeta_link, versioned_llvm_target};
use crate::base::needs_allocator_shim_for_linking;
Expand Down Expand Up @@ -86,6 +87,7 @@ pub fn link_binary(
let _timer = sess.timer("link_binary");
let output_metadata = sess.opts.output_types.contains_key(&OutputType::Metadata);
let mut tempfiles_for_stdout_output: Vec<PathBuf> = Vec::new();
let mut rmeta_link_cache = RmetaLinkCache::default();
for &crate_type in &crate_info.crate_types {
// Ignore executable crates if we have -Z no-codegen, as they will error.
if (sess.opts.unstable_opts.no_codegen || !sess.opts.output_types.should_codegen())
Expand Down Expand Up @@ -139,6 +141,7 @@ pub fn link_binary(
link_staticlib(
sess,
archive_builder_builder,
&mut rmeta_link_cache,
&compiled_modules,
&crate_info,
&metadata,
Expand All @@ -150,6 +153,7 @@ pub fn link_binary(
link_natively(
sess,
archive_builder_builder,
&mut rmeta_link_cache,
crate_type,
&out_filename,
&compiled_modules,
Expand Down Expand Up @@ -502,6 +506,7 @@ fn link_rlib<'a>(
fn link_staticlib(
sess: &Session,
archive_builder_builder: &dyn ArchiveBuilderBuilder,
rmeta_link_cache: &mut RmetaLinkCache,
compiled_modules: &CompiledModules,
crate_info: &CrateInfo,
metadata: &EncodedMetadata,
Expand Down Expand Up @@ -531,7 +536,7 @@ fn link_staticlib(
let bundled_libs: FxIndexSet<_> = native_libs.filter_map(|lib| lib.filename).collect();
ab.add_archive(
path,
AddArchiveKind::Rlib(&|fname: &str, entry_kind| {
AddArchiveKind::Rlib(rmeta_link_cache, &|fname: &str, entry_kind| {
// Ignore metadata and rmeta-link files.
if fname == METADATA_FILENAME || fname == rmeta_link::FILENAME {
return true;
Expand Down Expand Up @@ -939,6 +944,7 @@ fn report_linker_output(
fn link_natively(
sess: &Session,
archive_builder_builder: &dyn ArchiveBuilderBuilder,
rmeta_link_cache: &mut RmetaLinkCache,
crate_type: CrateType,
out_filename: &Path,
compiled_modules: &CompiledModules,
Expand All @@ -965,6 +971,7 @@ fn link_natively(
flavor,
sess,
archive_builder_builder,
rmeta_link_cache,
crate_type,
tmpdir,
temp_filename,
Expand Down Expand Up @@ -2562,6 +2569,7 @@ fn linker_with_args(
flavor: LinkerFlavor,
sess: &Session,
archive_builder_builder: &dyn ArchiveBuilderBuilder,
rmeta_link_cache: &mut RmetaLinkCache,
crate_type: CrateType,
tmpdir: &Path,
out_filename: &Path,
Expand Down Expand Up @@ -2690,6 +2698,7 @@ fn linker_with_args(
cmd,
sess,
archive_builder_builder,
rmeta_link_cache,
crate_info,
crate_type,
tmpdir,
Expand Down Expand Up @@ -3126,6 +3135,7 @@ fn add_upstream_rust_crates(
cmd: &mut dyn Linker,
sess: &Session,
archive_builder_builder: &dyn ArchiveBuilderBuilder,
rmeta_link_cache: &mut RmetaLinkCache,
crate_info: &CrateInfo,
crate_type: CrateType,
tmpdir: &Path,
Expand Down Expand Up @@ -3178,6 +3188,7 @@ fn add_upstream_rust_crates(
cmd,
sess,
archive_builder_builder,
rmeta_link_cache,
crate_info,
tmpdir,
cnum,
Expand Down Expand Up @@ -3309,6 +3320,7 @@ fn add_static_crate(
cmd: &mut dyn Linker,
sess: &Session,
archive_builder_builder: &dyn ArchiveBuilderBuilder,
rmeta_link_cache: &mut RmetaLinkCache,
crate_info: &CrateInfo,
tmpdir: &Path,
cnum: CrateNum,
Expand Down Expand Up @@ -3339,7 +3351,7 @@ fn add_static_crate(
let mut archive = archive_builder_builder.new_archive_builder(sess);
if let Err(error) = archive.add_archive(
cratepath,
AddArchiveKind::Rlib(&|f, entry_kind| {
AddArchiveKind::Rlib(rmeta_link_cache, &|f, entry_kind| {
if f == METADATA_FILENAME || f == rmeta_link::FILENAME {
return true;
}
Expand Down
18 changes: 17 additions & 1 deletion compiler/rustc_codegen_ssa/src/back/rmeta_link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
//! and potentially other data collected and used when building or linking a rlib.
//! See <https://github.com/rust-lang/rust/issues/138243>.

use std::path::Path;
use std::path::{Path, PathBuf};

use object::read::archive::ArchiveFile;
use rustc_data_structures::fx::FxHashMap;
use rustc_serialize::opaque::mem_encoder::MemEncoder;
use rustc_serialize::opaque::{MAGIC_END_BYTES, MemDecoder};
use rustc_serialize::{Decodable, Encodable};
Expand Down Expand Up @@ -54,3 +55,18 @@ pub fn read_from_data(archive_data: &[u8], rlib_path: &Path) -> Option<RmetaLink
let archive = ArchiveFile::parse(archive_data).ok()?;
read(&archive, archive_data, rlib_path)
}

#[derive(Default)]
pub struct RmetaLinkCache {
cache: FxHashMap<PathBuf, Option<RmetaLink>>,
}

impl RmetaLinkCache {
pub fn get_or_insert_with(
&mut self,
rlib_path: &Path,
load: impl FnOnce() -> Option<RmetaLink>,
) -> Option<&RmetaLink> {
self.cache.entry(rlib_path.to_path_buf()).or_insert_with(load).as_ref()
}
}
2 changes: 1 addition & 1 deletion compiler/rustc_query_impl/src/handle_cycle_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ pub(crate) fn variances_of<'tcx>(
err: Diag<'_>,
) -> &'tcx [ty::Variance] {
let _guar = err.delay_as_bug();
let n = tcx.generics_of(def_id).own_params.len();
let n = tcx.generics_of(def_id).count();
tcx.arena.alloc_from_iter(iter::repeat_n(ty::Bivariant, n))
}

Expand Down
16 changes: 16 additions & 0 deletions src/librustdoc/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,20 @@ declare_rustdoc_lint! {
"detects redundant explicit links in doc comments"
}

declare_rustdoc_lint! {
/// This lint checks for uses of footnote references without definition.
BROKEN_FOOTNOTE,
Warn,
"detects footnote references with no associated definition"
}

declare_rustdoc_lint! {
/// This lint checks if all footnote definitions are used.
UNUSED_FOOTNOTE_DEFINITION,
Warn,
"detects unused footnote definitions"
}

pub(crate) static RUSTDOC_LINTS: Lazy<Vec<&'static Lint>> = Lazy::new(|| {
vec![
BROKEN_INTRA_DOC_LINKS,
Expand All @@ -209,6 +223,8 @@ pub(crate) static RUSTDOC_LINTS: Lazy<Vec<&'static Lint>> = Lazy::new(|| {
MISSING_CRATE_LEVEL_DOCS,
UNESCAPED_BACKTICKS,
REDUNDANT_EXPLICIT_LINKS,
BROKEN_FOOTNOTE,
UNUSED_FOOTNOTE_DEFINITION,
]
});

Expand Down
2 changes: 2 additions & 0 deletions src/librustdoc/passes/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

mod bare_urls;
mod check_code_block_syntax;
mod footnotes;
mod html_tags;
mod redundant_explicit_links;
mod unescaped_backticks;
Expand Down Expand Up @@ -41,6 +42,7 @@ impl DocVisitor<'_> for Linter<'_, '_> {
if may_have_link {
bare_urls::visit_item(self.cx, item, hir_id, &dox);
redundant_explicit_links::visit_item(self.cx, item, hir_id);
footnotes::visit_item(self.cx, item, hir_id, &dox);
}
if may_have_code {
check_code_block_syntax::visit_item(self.cx, item, &dox);
Expand Down
117 changes: 117 additions & 0 deletions src/librustdoc/passes/lint/footnotes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use std::ops::Range;

use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::DiagDecorator;
use rustc_hir::HirId;
use rustc_lint_defs::Applicability;
use rustc_resolve::rustdoc::pulldown_cmark::{Event, Options, Parser, Tag};
use rustc_resolve::rustdoc::source_span_for_markdown_range;

use crate::clean::Item;
use crate::core::DocContext;

pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
let tcx = cx.tcx;

let mut missing_footnote_references = FxHashSet::default();
let mut footnote_references = FxHashSet::default();
let mut footnote_definitions = FxHashMap::default();

let options = Options::ENABLE_FOOTNOTES;
let mut parser = Parser::new_ext(dox, options).into_offset_iter().peekable();
while let Some((event, span)) = parser.next() {
match event {
Event::Text(text)
if &*text == "["
&& (span.start == 0 || dox.as_bytes().get(span.start - 1) != Some(&b'\\'))
&& let Some(len) = scan_footnote_ref(&dox[span.start..]) =>
{
missing_footnote_references
.insert(Range { start: span.start, end: span.start + len });
}
Event::FootnoteReference(label) => {
footnote_references.insert(label);
}
Event::Start(Tag::FootnoteDefinition(label)) => {
footnote_definitions.insert(label, span.start + 1);
}
_ => {}
}
}

#[allow(rustc::potential_query_instability)]
for (footnote, span) in footnote_definitions {
if !footnote_references.contains(&footnote) {
let (span, _) = source_span_for_markdown_range(
tcx,
dox,
&(span..span + 1),
&item.attrs.doc_strings,
)
.unwrap_or_else(|| (item.attr_span(tcx), false));

tcx.emit_node_span_lint(
crate::lint::UNUSED_FOOTNOTE_DEFINITION,
hir_id,
span,
DiagDecorator(|lint| {
lint.primary_message("unused footnote definition");
}),
);
}
}

#[allow(rustc::potential_query_instability)]
for span in missing_footnote_references {
let ref_span = source_span_for_markdown_range(tcx, dox, &span, &item.attrs.doc_strings)
.map(|(span, _)| span)
.unwrap_or_else(|| item.attr_span(tcx));

tcx.emit_node_span_lint(
crate::lint::BROKEN_FOOTNOTE,
hir_id,
ref_span,
DiagDecorator(|lint| {
lint.primary_message("no footnote definition matching this footnote");
lint.span_suggestion(
ref_span.shrink_to_lo(),
"if it should not be a footnote, escape it",
"\\",
Applicability::MaybeIncorrect,
);
}),
);
}
}

fn scan_footnote_ref(dox: &str) -> Option<usize> {
let dox = dox.as_bytes();
let mut i = 0;
if dox.get(i) != Some(&b'[') {
return None;
}
i += 1;
if dox.get(i) != Some(&b'^') {
return None;
}
i += 1;
while let Some(&c) = dox.get(i) {
if c == b']' {
i += 1;
return Some(i);
}
if c == b'\r' || c == b'\n' || c == b'[' {
// Can't nest things like this.
break;
}
if c == b'\\' {
i += 1;
}
if dox.get(i) == Some(&b'\r') || dox.get(i) == Some(&b'\n') {
// Can't have line breaks in footnote refs
break;
}
i += 1;
}
None
}
2 changes: 1 addition & 1 deletion src/rustdoc-json-types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "rustdoc-json-types"
version = "0.1.0"
edition = "2021"
edition = "2024"

[lib]
path = "lib.rs"
Expand Down
Loading
Loading