Skip to content

Commit a571179

Browse files
committed
Auto merge of #95542 - xFrednet:rfc-2383-expect-query, r=wesleywiser
Support tool lints with the `#[expect]` attribute (RFC 2383) This PR fixes the ICE #94953 by making the assert for converted expectation IDs conditional. Additionally, it moves the lint expectation check into a separate query to support rustdoc and other tools. On the way, I've also added some tests to ensure that the attribute works for Clippy and rustdoc lints. The number of changes comes from the long test file. This may look like a monster PR, this may smell like a monster PR and this may be a monster PR, but it's a harmless monster. 🦕 --- Closes: #94953 cc: #85549 r? `@wesleywiser` cc: `@rust-lang/rustdoc`
2 parents cb12198 + 9516a40 commit a571179

File tree

18 files changed

+663
-19
lines changed

18 files changed

+663
-19
lines changed

compiler/rustc_errors/src/lib.rs

+17-6
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,13 @@ struct HandlerInner {
426426

427427
future_breakage_diagnostics: Vec<Diagnostic>,
428428

429+
/// The [`Self::unstable_expect_diagnostics`] should be empty when this struct is
430+
/// dropped. However, it can have values if the compilation is stopped early
431+
/// or is only partially executed. To avoid ICEs, like in rust#94953 we only
432+
/// check if [`Self::unstable_expect_diagnostics`] is empty, if the expectation ids
433+
/// have been converted.
434+
check_unstable_expect_diagnostics: bool,
435+
429436
/// Expected [`Diagnostic`]s store a [`LintExpectationId`] as part of
430437
/// the lint level. [`LintExpectationId`]s created early during the compilation
431438
/// (before `HirId`s have been defined) are not stable and can therefore not be
@@ -497,10 +504,12 @@ impl Drop for HandlerInner {
497504
);
498505
}
499506

500-
assert!(
501-
self.unstable_expect_diagnostics.is_empty(),
502-
"all diagnostics with unstable expectations should have been converted",
503-
);
507+
if self.check_unstable_expect_diagnostics {
508+
assert!(
509+
self.unstable_expect_diagnostics.is_empty(),
510+
"all diagnostics with unstable expectations should have been converted",
511+
);
512+
}
504513
}
505514
}
506515

@@ -574,6 +583,7 @@ impl Handler {
574583
emitted_diagnostics: Default::default(),
575584
stashed_diagnostics: Default::default(),
576585
future_breakage_diagnostics: Vec::new(),
586+
check_unstable_expect_diagnostics: false,
577587
unstable_expect_diagnostics: Vec::new(),
578588
fulfilled_expectations: Default::default(),
579589
}),
@@ -988,12 +998,13 @@ impl Handler {
988998
&self,
989999
unstable_to_stable: &FxHashMap<LintExpectationId, LintExpectationId>,
9901000
) {
991-
let diags = std::mem::take(&mut self.inner.borrow_mut().unstable_expect_diagnostics);
1001+
let mut inner = self.inner.borrow_mut();
1002+
let diags = std::mem::take(&mut inner.unstable_expect_diagnostics);
1003+
inner.check_unstable_expect_diagnostics = true;
9921004
if diags.is_empty() {
9931005
return;
9941006
}
9951007

996-
let mut inner = self.inner.borrow_mut();
9971008
for mut diag in diags.into_iter() {
9981009
diag.update_unstable_expectation_id(unstable_to_stable);
9991010

compiler/rustc_interface/src/passes.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,10 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> {
10091009
});
10101010
}
10111011
);
1012+
1013+
// This check has to be run after all lints are done processing. We don't
1014+
// define a lint filter, as all lint checks should have finished at this point.
1015+
sess.time("check_lint_expectations", || tcx.check_expectations(None));
10121016
});
10131017

10141018
Ok(())

compiler/rustc_lint/src/expect.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
use crate::builtin;
22
use rustc_hir::HirId;
3+
use rustc_middle::ty::query::Providers;
34
use rustc_middle::{lint::LintExpectation, ty::TyCtxt};
45
use rustc_session::lint::LintExpectationId;
56
use rustc_span::symbol::sym;
7+
use rustc_span::Symbol;
68

7-
pub fn check_expectations(tcx: TyCtxt<'_>) {
9+
pub(crate) fn provide(providers: &mut Providers) {
10+
*providers = Providers { check_expectations, ..*providers };
11+
}
12+
13+
fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option<Symbol>) {
814
if !tcx.sess.features_untracked().enabled(sym::lint_reasons) {
915
return;
1016
}
@@ -13,7 +19,9 @@ pub fn check_expectations(tcx: TyCtxt<'_>) {
1319
let lint_expectations = &tcx.lint_levels(()).lint_expectations;
1420

1521
for (id, expectation) in lint_expectations {
16-
if !fulfilled_expectations.contains(id) {
22+
if !fulfilled_expectations.contains(id)
23+
&& tool_filter.map_or(true, |filter| expectation.lint_tool == Some(filter))
24+
{
1725
// This check will always be true, since `lint_expectations` only
1826
// holds stable ids
1927
if let LintExpectationId::Stable { hir_id, .. } = id {

compiler/rustc_lint/src/late.rs

-3
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,4 @@ pub fn check_crate<'tcx, T: LateLintPass<'tcx>>(
503503
});
504504
},
505505
);
506-
507-
// This check has to be run after all lints are done processing for this crate
508-
tcx.sess.time("check_lint_expectations", || crate::expect::check_expectations(tcx));
509506
}

compiler/rustc_lint/src/levels.rs

+18-7
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,12 @@ impl<'s> LintLevelsBuilder<'s> {
371371
};
372372
self.lint_expectations.push((
373373
expect_id,
374-
LintExpectation::new(reason, sp, is_unfulfilled_lint_expectations),
374+
LintExpectation::new(
375+
reason,
376+
sp,
377+
is_unfulfilled_lint_expectations,
378+
tool_name,
379+
),
375380
));
376381
}
377382
let src = LintLevelSource::Node(
@@ -400,8 +405,10 @@ impl<'s> LintLevelsBuilder<'s> {
400405
self.insert_spec(*id, (level, src));
401406
}
402407
if let Level::Expect(expect_id) = level {
403-
self.lint_expectations
404-
.push((expect_id, LintExpectation::new(reason, sp, false)));
408+
self.lint_expectations.push((
409+
expect_id,
410+
LintExpectation::new(reason, sp, false, tool_name),
411+
));
405412
}
406413
}
407414
Err((Some(ids), ref new_lint_name)) => {
@@ -444,8 +451,10 @@ impl<'s> LintLevelsBuilder<'s> {
444451
self.insert_spec(*id, (level, src));
445452
}
446453
if let Level::Expect(expect_id) = level {
447-
self.lint_expectations
448-
.push((expect_id, LintExpectation::new(reason, sp, false)));
454+
self.lint_expectations.push((
455+
expect_id,
456+
LintExpectation::new(reason, sp, false, tool_name),
457+
));
449458
}
450459
}
451460
Err((None, _)) => {
@@ -550,8 +559,10 @@ impl<'s> LintLevelsBuilder<'s> {
550559
}
551560
}
552561
if let Level::Expect(expect_id) = level {
553-
self.lint_expectations
554-
.push((expect_id, LintExpectation::new(reason, sp, false)));
562+
self.lint_expectations.push((
563+
expect_id,
564+
LintExpectation::new(reason, sp, false, tool_name),
565+
));
555566
}
556567
} else {
557568
panic!("renamed lint does not exist: {}", new_name);

compiler/rustc_lint/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ pub use rustc_session::lint::{LintArray, LintPass};
109109

110110
pub fn provide(providers: &mut Providers) {
111111
levels::provide(providers);
112+
expect::provide(providers);
112113
*providers = Providers { lint_mod, ..*providers };
113114
}
114115

compiler/rustc_middle/src/lint.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -210,15 +210,20 @@ pub struct LintExpectation {
210210
/// adjusted to include an additional note. Therefore, we have to track if
211211
/// the expectation is for the lint.
212212
pub is_unfulfilled_lint_expectations: bool,
213+
/// This will hold the name of the tool that this lint belongs to. For
214+
/// the lint `clippy::some_lint` the tool would be `clippy`, the same
215+
/// goes for `rustdoc`. This will be `None` for rustc lints
216+
pub lint_tool: Option<Symbol>,
213217
}
214218

215219
impl LintExpectation {
216220
pub fn new(
217221
reason: Option<Symbol>,
218222
emission_span: Span,
219223
is_unfulfilled_lint_expectations: bool,
224+
lint_tool: Option<Symbol>,
220225
) -> Self {
221-
Self { reason, emission_span, is_unfulfilled_lint_expectations }
226+
Self { reason, emission_span, is_unfulfilled_lint_expectations, lint_tool }
222227
}
223228
}
224229

compiler/rustc_middle/src/query/mod.rs

+19
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,25 @@ rustc_queries! {
157157
desc { "running analysis passes on this crate" }
158158
}
159159

160+
/// This query checks the fulfillment of collected lint expectations.
161+
/// All lint emitting queries have to be done before this is executed
162+
/// to ensure that all expectations can be fulfilled.
163+
///
164+
/// This is an extra query to enable other drivers (like rustdoc) to
165+
/// only execute a small subset of the `analysis` query, while allowing
166+
/// lints to be expected. In rustc, this query will be executed as part of
167+
/// the `analysis` query and doesn't have to be called a second time.
168+
///
169+
/// Tools can additionally pass in a tool filter. That will restrict the
170+
/// expectations to only trigger for lints starting with the listed tool
171+
/// name. This is useful for cases were not all linting code from rustc
172+
/// was called. With the default `None` all registered lints will also
173+
/// be checked for expectation fulfillment.
174+
query check_expectations(key: Option<Symbol>) -> () {
175+
eval_always
176+
desc { "checking lint expectations (RFC 2383)" }
177+
}
178+
160179
/// Maps from the `DefId` of an item (trait/struct/enum/fn) to its
161180
/// associated generics.
162181
query generics_of(key: DefId) -> ty::Generics {

compiler/rustc_query_impl/src/keys.rs

+10
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,16 @@ impl Key for Symbol {
435435
}
436436
}
437437

438+
impl Key for Option<Symbol> {
439+
#[inline(always)]
440+
fn query_crate_is_local(&self) -> bool {
441+
true
442+
}
443+
fn default_span(&self, _tcx: TyCtxt<'_>) -> Span {
444+
DUMMY_SP
445+
}
446+
}
447+
438448
/// Canonical query goals correspond to abstract trait operations that
439449
/// are not tied to any crate in particular.
440450
impl<'tcx, T> Key for Canonical<'tcx, T> {

src/librustdoc/core.rs

+4
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ crate fn create_config(
232232
rustc_lint::builtin::RENAMED_AND_REMOVED_LINTS.name.to_string(),
233233
rustc_lint::builtin::UNKNOWN_LINTS.name.to_string(),
234234
rustc_lint::builtin::UNEXPECTED_CFGS.name.to_string(),
235+
// this lint is needed to support `#[expect]` attributes
236+
rustc_lint::builtin::UNFULFILLED_LINT_EXPECTATIONS.name.to_string(),
235237
];
236238
lints_to_show.extend(crate::lint::RUSTDOC_LINTS.iter().map(|lint| lint.name.to_string()));
237239

@@ -463,6 +465,8 @@ crate fn run_global_ctxt(
463465
}
464466
}
465467

468+
tcx.sess.time("check_lint_expectations", || tcx.check_expectations(Some(sym::rustdoc)));
469+
466470
if tcx.sess.diagnostic().has_errors_or_lint_errors().is_some() {
467471
rustc_errors::FatalError.raise();
468472
}

0 commit comments

Comments
 (0)