Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
31e436d
Add new rustdoc `broken_footnote` lint
GuillaumeGomez Mar 11, 2025
37b8c53
Add ui test for rustdoc `broken_footnote` lint
GuillaumeGomez Mar 11, 2025
c74e84a
Add new `unused_footnote_definition` rustdoc lint
GuillaumeGomez Mar 11, 2025
9f30b84
Add ui test for new `unused_footnote_definition` rustdoc lint
GuillaumeGomez Mar 11, 2025
261a213
Improve description of new rustdoc lints
GuillaumeGomez Jan 26, 2026
4420714
Remove outdated comment
GuillaumeGomez Feb 25, 2026
92dcc5b
Add extra "broken_footnote" lint ui test
GuillaumeGomez Mar 12, 2026
7253b43
Fix backslashes and line breaks in footnote lint
notriddle Jun 27, 2026
e736996
Split doc comment so lints are always emitted in the right order
GuillaumeGomez Aug 12, 2026
116fdd1
Fix tidy error
GuillaumeGomez Aug 12, 2026
5b3e277
Remove target argument from get_proc_macros
bjorn3 Aug 12, 2026
8447565
self-profile more of borrowck
lqd Aug 12, 2026
349eac6
rustc_parse: suggest removing semicolon before `if` block
ravlynd Aug 10, 2026
2982507
Remove old cfg parser which is now dead code
GuillaumeGomez Aug 12, 2026
b42e08e
Rollup merge of #160975 - bjorn3:proc_macro_refactors6, r=Mark-Simula…
JonathanBrouwer Aug 12, 2026
09c5af0
Rollup merge of #160985 - lqd:borrowck-self-profile, r=jackh726
JonathanBrouwer Aug 12, 2026
e3ce623
Rollup merge of #137858 - GuillaumeGomez:unused_footnote_def, r=notri…
JonathanBrouwer Aug 12, 2026
165f76a
Rollup merge of #160861 - ravlynd:if-semi-before-block-suggestion, r=…
JonathanBrouwer Aug 12, 2026
b186ca7
Rollup merge of #160990 - GuillaumeGomez:old-cfg-dead-code, r=Jonatha…
JonathanBrouwer Aug 12, 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
35 changes: 20 additions & 15 deletions compiler/rustc_borrowck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -612,21 +612,26 @@ fn get_flow_results<'a, 'tcx>(
) -> Results<'tcx, Borrowck<'a, 'tcx>> {
// We compute these three analyses individually, but them combine them into
// a single results so that `mbcx` can visit them all together.
let borrows = Borrows::new(tcx, body, regioncx, borrow_set).iterate_to_fixpoint(
tcx,
body,
Some("borrowck"),
);
let uninits = MaybeUninitializedPlaces::new(tcx, body, move_data).iterate_to_fixpoint(
tcx,
body,
Some("borrowck"),
);
let ever_inits = EverInitializedPlaces::new(body, move_data).iterate_to_fixpoint(
tcx,
body,
Some("borrowck"),
);
let borrows = {
let _timer = tcx.prof.generic_activity("borrowck_dataflow_borrows");
Borrows::new(tcx, body, regioncx, borrow_set).iterate_to_fixpoint(
tcx,
body,
Some("borrowck"),
)
};
let uninits = {
let _timer = tcx.prof.generic_activity("borrowck_dataflow_maybe_uninits");
MaybeUninitializedPlaces::new(tcx, body, move_data).iterate_to_fixpoint(
tcx,
body,
Some("borrowck"),
)
};
let ever_inits = {
let _timer = tcx.prof.generic_activity("borrowck_dataflow_ever_inits");
EverInitializedPlaces::new(body, move_data).iterate_to_fixpoint(tcx, body, Some("borrowck"))
};

let analysis = Borrowck {
borrows: borrows.analysis,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_borrowck/src/nll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ pub(crate) fn compute_regions<'tcx>(
// If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives constraints
// and use them to compute loan liveness.
if let Some(polonius_context) = polonius_context.as_mut() {
let _timer = infcx.tcx.prof.generic_activity("borrowck_polonius_loan_liveness");
polonius_context.compute_loan_liveness(&mut regioncx, body, borrow_set)
}

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_borrowck/src/type_check/liveness/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub(super) fn generate<'tcx>(
move_data: &MoveData<'tcx>,
) {
debug!("liveness::generate");
let _timer = typeck.tcx().prof.generic_activity("borrowck_liveness");

let mut free_regions = regions_that_outlive_free_regions(
typeck.infcx.num_region_vars(),
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_borrowck/src/type_check/liveness/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ pub(super) fn trace<'tcx>(
relevant_live_locals: Vec<Local>,
boring_locals: Vec<Local>,
) {
let _timer = typeck.tcx().prof.generic_activity("borrowck_liveness_trace");

let local_use_map = &LocalUseMap::build(&relevant_live_locals, location_map, typeck.body);
let cx = LivenessContext {
typeck,
Expand Down Expand Up @@ -485,6 +487,7 @@ impl<'a, 'typeck, 'tcx> LivenessContext<'a, 'typeck, 'tcx> {
// a much, much smaller domain: in our benchmarks, when it's not zero (the most likely
// case), there are a few dozens compared to e.g. thousands or tens of thousands of
// locals and move paths.
let _timer = tcx.prof.generic_activity("borrowck_dataflow_maybe_inits");
let flow_inits = MaybeInitializedPlaces::new(tcx, body, self.move_data)
.iterate_to_fixpoint(tcx, body, Some("borrowck"))
.into_results_cursor(body);
Expand Down
33 changes: 2 additions & 31 deletions compiler/rustc_expand/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ use rustc_ast::token::{Delimiter, Token, TokenKind};
use rustc_ast::tokenstream::{
AttrTokenStream, AttrTokenTree, LazyAttrTokenStream, Spacing, TokenTree, WithTokens,
};
use rustc_ast::{
self as ast, AttrStyle, Attribute, HasAttrs, HasTokens, MetaItem, MetaItemInner, NodeId,
SyntheticAttr,
};
use rustc_ast::{self as ast, AttrStyle, Attribute, HasAttrs, HasTokens, NodeId, SyntheticAttr};
use rustc_attr_ir::target::Target;
use rustc_attr_ir::{self as attrs, AttributeKind};
use rustc_attr_parsing::parser::AllowExprMetavar;
Expand All @@ -32,7 +29,7 @@ use tracing::instrument;

use crate::diagnostics::{
CrateNameInCfgAttr, CrateTypeInCfgAttr, FeatureNotAllowed, FeatureRemoved,
FeatureRemovedReason, InvalidCfg, RemoveExprNotSupported,
FeatureRemovedReason, RemoveExprNotSupported,
};

/// A folder that strips out items that do not belong in the current configuration.
Expand Down Expand Up @@ -442,32 +439,6 @@ impl<'a> StripUnconfigured<'a> {
}
}

/// FIXME: Still used by Rustdoc, should be removed after
pub fn parse_cfg_old<'a>(meta_item: &'a MetaItem, sess: &Session) -> Option<&'a MetaItemInner> {
let span = meta_item.span;
match meta_item.meta_item_list() {
None => {
sess.dcx().emit_err(InvalidCfg::NotFollowedByParens { span });
None
}
Some([]) => {
sess.dcx().emit_err(InvalidCfg::NoPredicate { span });
None
}
Some([_, .., l]) => {
sess.dcx().emit_err(InvalidCfg::MultiplePredicates { span: l.span() });
None
}
Some([single]) => match single.meta_item_or_bool() {
Some(meta_item) => Some(meta_item),
None => {
sess.dcx().emit_err(InvalidCfg::PredicateLiteral { span: single.span() });
None
}
},
}
}

fn is_cfg(attr: &Attribute) -> bool {
attr.has_name(sym::cfg)
}
34 changes: 0 additions & 34 deletions compiler/rustc_expand/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,40 +189,6 @@ pub(crate) struct RemoveExprNotSupported {
pub span: Span,
}

#[derive(Diagnostic)]
pub(crate) enum InvalidCfg {
#[diag("`cfg` is not followed by parentheses")]
NotFollowedByParens {
#[primary_span]
#[suggestion(
"expected syntax is",
code = "cfg(/* predicate */)",
applicability = "has-placeholders"
)]
span: Span,
},
#[diag("`cfg` predicate is not specified")]
NoPredicate {
#[primary_span]
#[suggestion(
"expected syntax is",
code = "cfg(/* predicate */)",
applicability = "has-placeholders"
)]
span: Span,
},
#[diag("multiple `cfg` predicates are specified")]
MultiplePredicates {
#[primary_span]
span: Span,
},
#[diag("`cfg` predicate key cannot be a literal")]
PredicateLiteral {
#[primary_span]
span: Span,
},
}

#[derive(Diagnostic)]
#[diag("non-{$kind} macro in {$kind} position: {$name}")]
pub(crate) struct WrongFragmentKind<'a> {
Expand Down
8 changes: 5 additions & 3 deletions compiler/rustc_metadata/src/locator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -970,17 +970,19 @@ fn get_flavor_from_path(path: &Path) -> CrateFlavor {
}
}

/// A function to fetch about all macros inside a proc-macro crate.
/// A function to fetch all macros inside a proc-macro crate.
///
/// Used by rust-analyzer-proc-macro-srv.
pub fn get_proc_macros(
target: &Target,
path: &Path,
metadata_loader: &dyn MetadataLoader,
cfg_version: &'static str,
) -> IoResult<Vec<(ProcMacroClient, ProcMacroKind)>> {
let host_tuple = TargetTuple::from_tuple(config::host_tuple());
let (host, _) = Target::search(&host_tuple, Path::new(""), false).unwrap();

let metadata =
get_metadata_section(target, CrateFlavor::Dylib, path, metadata_loader, cfg_version, None)
get_metadata_section(&host, CrateFlavor::Dylib, path, metadata_loader, cfg_version, None)
.map_err(|err| io::Error::other(err.to_string()))?;
let stable_crate_id = metadata.get_root().stable_crate_id();

Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2777,6 +2777,17 @@ impl<'a> Parser<'a> {
"you likely meant to continue parsing the let-chain starting here",
);
} else {
if self.prev_token == token::Semi
&& (self.token == token::OpenBrace || AssocOp::from_token(&self.token).is_some())
{
err.span_suggestion_verbose(
self.prev_token.span,
"remove this semicolon",
"",
Applicability::MaybeIncorrect,
);
}

// Look for usages of '=>' where '>=' might be intended
if maybe_fatarrow == token::FatArrow {
err.span_suggestion_verbose(
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
}
7 changes: 0 additions & 7 deletions src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ mod proc_macros;
use rustc_codegen_ssa::back::metadata::DefaultMetadataLoader;
use rustc_interface::util::rustc_version_str;
use rustc_proc_macro::bridge;
use rustc_session::config::host_tuple;
use rustc_target::spec::{Target, TargetTuple};
use std::path::Path;
use std::{fs, io, time::SystemTime};
use temp_dir::TempDir;

Expand Down Expand Up @@ -78,11 +75,7 @@ struct ProcMacroLibrary {
impl ProcMacroLibrary {
fn open(path: &Utf8Path) -> io::Result<Self> {
let proc_macros = rustc_span::create_default_session_globals_then(|| {
let (target, _) =
Target::search(&TargetTuple::from_tuple(host_tuple()), Path::new(""), false)
.unwrap();
rustc_metadata::locator::get_proc_macros(
&target,
path.as_ref(),
&DefaultMetadataLoader,
rustc_version_str().unwrap_or("unknown"),
Expand Down
Loading
Loading