/// This methods steals all [`LintExpectationId`]s that are stored inside
/// [`HandlerInner`] and indicate that the linked expectation has been fulfilled.
+ #[must_use]
pub fn steal_fulfilled_expectation_ids(&self) -> FxHashSet<LintExpectationId> {
assert!(
self.inner.borrow().unstable_expect_diagnostics.is_empty(),
hir_id: HirId,
expectation: &LintExpectation,
) {
- // FIXME: The current implementation doesn't cover cases where the
- // `unfulfilled_lint_expectations` is actually expected by another lint
- // expectation. This can be added here by checking the lint level and
- // retrieving the `LintExpectationId` if it was expected.
tcx.struct_span_lint_hir(
builtin::UNFULFILLED_LINT_EXPECTATIONS,
hir_id,
if let Some(rationale) = expectation.reason {
diag.note(&rationale.as_str());
}
+
+ if expectation.is_unfulfilled_lint_expectations {
+ diag.note("the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message");
+ }
+
diag.emit();
},
);
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::{RegisteredTools, TyCtxt};
use rustc_session::lint::{
- builtin::{self, FORBIDDEN_LINT_GROUPS},
+ builtin::{self, FORBIDDEN_LINT_GROUPS, UNFULFILLED_LINT_EXPECTATIONS},
Level, Lint, LintExpectationId, LintId,
};
use rustc_session::parse::feature_err;
}
}
}
+
+ // The lint `unfulfilled_lint_expectations` can't be expected, as it would suppress itself.
+ // Handling expectations of this lint would add additional complexity with little to no
+ // benefit. The expect level for this lint will therefore be ignored.
+ if let Level::Expect(_) = level && id == LintId::of(UNFULFILLED_LINT_EXPECTATIONS) {
+ return;
+ }
+
if let Level::ForceWarn = old_level {
specs.insert(id, (old_level, old_src));
} else {
self.store.check_lint_name(&name, tool_name, self.registered_tools);
match &lint_result {
CheckLintNameResult::Ok(ids) => {
+ // This checks for instances where the user writes `#[expect(unfulfilled_lint_expectations)]`
+ // in that case we want to avoid overriding the lint level but instead add an expectation that
+ // can't be fulfilled. The lint message will include an explanation, that the
+ // `unfulfilled_lint_expectations` lint can't be expected.
+ if let Level::Expect(expect_id) = level {
+ let is_unfulfilled_lint_expectations = match ids {
+ [lint] => *lint == LintId::of(UNFULFILLED_LINT_EXPECTATIONS),
+ _ => false,
+ };
+ self.lint_expectations.push((
+ expect_id,
+ LintExpectation::new(reason, sp, is_unfulfilled_lint_expectations),
+ ));
+ }
let src = LintLevelSource::Node(
meta_item.path.segments.last().expect("empty lint name").ident.name,
sp,
self.check_gated_lint(id, attr.span);
self.insert_spec(&mut specs, id, (level, src));
}
- if let Level::Expect(expect_id) = level {
- self.lint_expectations
- .push((expect_id, LintExpectation::new(reason, sp)));
- }
}
CheckLintNameResult::Tool(result) => {
}
if let Level::Expect(expect_id) = level {
self.lint_expectations
- .push((expect_id, LintExpectation::new(reason, sp)));
+ .push((expect_id, LintExpectation::new(reason, sp, false)));
}
}
Err((Some(ids), ref new_lint_name)) => {
}
if let Level::Expect(expect_id) = level {
self.lint_expectations
- .push((expect_id, LintExpectation::new(reason, sp)));
+ .push((expect_id, LintExpectation::new(reason, sp, false)));
}
}
Err((None, _)) => {
}
if let Level::Expect(expect_id) = level {
self.lint_expectations
- .push((expect_id, LintExpectation::new(reason, sp)));
+ .push((expect_id, LintExpectation::new(reason, sp, false)));
}
} else {
panic!("renamed lint does not exist: {}", new_name);
pub reason: Option<Symbol>,
/// The [`Span`] of the attribute that this expectation originated from.
pub emission_span: Span,
+ /// Lint messages for the `unfulfilled_lint_expectations` lint will be
+ /// adjusted to include an additional note. Therefore, we have to track if
+ /// the expectation is for the lint.
+ pub is_unfulfilled_lint_expectations: bool,
}
impl LintExpectation {
- pub fn new(reason: Option<Symbol>, attr_span: Span) -> Self {
- Self { reason, emission_span: attr_span }
+ pub fn new(
+ reason: Option<Symbol>,
+ emission_span: Span,
+ is_unfulfilled_lint_expectations: bool,
+ ) -> Self {
+ Self { reason, emission_span, is_unfulfilled_lint_expectations }
}
}
--- /dev/null
+// check-pass
+// ignore-tidy-linelength
+
+#![feature(lint_reasons)]
+#![warn(unused_mut)]
+
+#![expect(unfulfilled_lint_expectations, reason = "idk why you would expect this")]
+//~^ WARNING this lint expectation is unfulfilled
+//~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default
+//~| NOTE idk why you would expect this
+//~| NOTE the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
+
+#[expect(unfulfilled_lint_expectations, reason = "a local: idk why you would expect this")]
+//~^ WARNING this lint expectation is unfulfilled
+//~| NOTE a local: idk why you would expect this
+//~| NOTE the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
+pub fn normal_test_fn() {
+ #[expect(unused_mut, reason = "this expectation will create a diagnostic with the default lint level")]
+ //~^ WARNING this lint expectation is unfulfilled
+ //~| NOTE this expectation will create a diagnostic with the default lint level
+ let mut v = vec![1, 1, 2, 3, 5];
+ v.sort();
+
+ // Check that lint lists including `unfulfilled_lint_expectations` are also handled correctly
+ #[expect(unused, unfulfilled_lint_expectations, reason = "the expectation for `unused` should be fulfilled")]
+ //~^ WARNING this lint expectation is unfulfilled
+ //~| NOTE the expectation for `unused` should be fulfilled
+ //~| NOTE the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
+ let value = "I'm unused";
+}
+
+#[expect(warnings, reason = "this suppresses all warnings and also suppresses itself. No warning will be issued")]
+pub fn expect_warnings() {
+ // This lint trigger will be suppressed
+ #[warn(unused_mut)]
+ let mut v = vec![1, 1, 2, 3, 5];
+}
+
+fn main() {}
--- /dev/null
+warning: this lint expectation is unfulfilled
+ --> $DIR/expect_unfulfilled_expectation.rs:7:11
+ |
+LL | #![expect(unfulfilled_lint_expectations, reason = "idk why you would expect this")]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `#[warn(unfulfilled_lint_expectations)]` on by default
+ = note: idk why you would expect this
+ = note: the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
+
+warning: this lint expectation is unfulfilled
+ --> $DIR/expect_unfulfilled_expectation.rs:13:10
+ |
+LL | #[expect(unfulfilled_lint_expectations, reason = "a local: idk why you would expect this")]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: a local: idk why you would expect this
+ = note: the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
+
+warning: this lint expectation is unfulfilled
+ --> $DIR/expect_unfulfilled_expectation.rs:18:14
+ |
+LL | #[expect(unused_mut, reason = "this expectation will create a diagnostic with the default lint level")]
+ | ^^^^^^^^^^
+ |
+ = note: this expectation will create a diagnostic with the default lint level
+
+warning: this lint expectation is unfulfilled
+ --> $DIR/expect_unfulfilled_expectation.rs:25:22
+ |
+LL | #[expect(unused, unfulfilled_lint_expectations, reason = "the expectation for `unused` should be fulfilled")]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the expectation for `unused` should be fulfilled
+ = note: the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
+
+warning: 4 warnings emitted
+