Auto merge of #87835 - xFrednet:rfc-2383-expect-attribute-with-ids, r=wesleywiser

Implementation of the `expect` attribute (RFC 2383)

This is an implementation of the `expect` attribute as described in [RFC-2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html). The attribute allows the suppression of lint message by expecting them. Unfulfilled lint expectations (meaning no expected lint was caught) will emit the `unfulfilled_lint_expectations` lint at the `expect` attribute.

### Example
#### input
```rs
// required feature flag
#![feature(lint_reasons)]

#[expect(unused_mut)] // Will warn about an unfulfilled expectation
#[expect(unused_variables)] // Will be fulfilled by x
fn main() {
    let x = 0;
}
```

#### output

```txt
warning: this lint expectation is unfulfilled
  --> $DIR/trigger_lint.rs:3:1
   |
LL | #[expect(unused_mut)] // Will warn about an unfulfilled expectation
   |          ^^^^^^^^^^
   |
   = note: `#[warn(unfulfilled_lint_expectations)]` on by default
```

### Implementation

This implementation introduces `Expect` as a new lint level for diagnostics, which have been expected. All lint expectations marked via the `expect` attribute are collected in the [`LintLevelsBuilder`] and assigned an ID that is stored in the new lint level. The `LintLevelsBuilder` stores all found expectations and the data needed to emit the `unfulfilled_lint_expectations` in the [`LintLevelsMap`] which is the result of the [`lint_levels()`] query.

The [`rustc_errors::HandlerInner`] is the central error handler in rustc and handles the emission of all diagnostics. Lint message with the level `Expect` are suppressed during this emission, while the expectation ID is stored in a set which marks them as fulfilled. The last step is then so simply check if all expectations collected by the [`LintLevelsBuilder`] in the [`LintLevelsMap`] have been marked as fulfilled in the [`rustc_errors::HandlerInner`]. Otherwise, a new lint message will be emitted.

The implementation of the `LintExpectationId` required some special handling to make it stable between sessions. Lints can be emitted during [`EarlyLintPass`]es. At this stage, it's not possible to create a stable identifier. The level instead stores an unstable identifier, which is later converted to a stable `LintExpectationId`.

### Followup TO-DOs
All open TO-DOs have been marked with `FIXME` comments in the code. This is the combined list of them:

* [ ] The current implementation doesn't cover cases where the `unfulfilled_lint_expectations` lint is actually expected by another `expect` attribute.
   * This should be easily possible, but I wanted to get some feedback before putting more work into this.
   * This could also be done in a new PR to not add to much more code to this one
* [ ] Update unstable documentation to reflect this change.
* [ ] Update unstable expectation ids in [`HandlerInner::stashed_diagnostics`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/struct.HandlerInner.html#structfield.stashed_diagnostics)

### Open questions
I also have a few open questions where I would like to get feedback on:
1. The RFC discussion included a suggestion to change the `expect` attribute to something else. (Initiated by `@Ixrec` [here](https://github.com/rust-lang/rfcs/pull/2383#issuecomment-378424091), suggestion from `@scottmcm` to use `#[should_lint(...)]` [here](https://github.com/rust-lang/rfcs/pull/2383#issuecomment-378648877)). No real conclusion was drawn on that point from my understanding. Is this still open for discussion, or was this discarded with the merge of the RFC?
2. How should the expect attribute deal with the new `force-warn` lint level?

---

This approach was inspired by a discussion with `@LeSeulArtichaut.`

RFC tracking issue: #54503

Mentoring/Implementation issue: #85549

[`LintLevelsBuilder`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/levels/struct.LintLevelsBuilder.html
[`LintLevelsMap`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/lint/struct.LintLevelMap.html
[`lint_levels()`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html#method.lint_levels
[`rustc_errors::HandlerInner`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/struct.HandlerInner.html
[`EarlyLintPass`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html
This commit is contained in:
bors 2022-03-03 18:59:32 +00:00
commit 10913c0001
37 changed files with 1040 additions and 26 deletions

View File

@ -3882,6 +3882,7 @@ version = "0.0.0"
dependencies = [
"rustc_ast",
"rustc_data_structures",
"rustc_hir",
"rustc_macros",
"rustc_serialize",
"rustc_span",

View File

@ -75,6 +75,7 @@ fn annotation_type_for_level(level: Level) -> AnnotationType {
// FIXME(#59346): Not sure how to map this level
Level::FailureNote => AnnotationType::Error,
Level::Allow => panic!("Should not call with Allow"),
Level::Expect(_) => panic!("Should not call with Expect"),
}
}

View File

@ -133,7 +133,7 @@ impl Diagnostic {
| Level::Error { .. }
| Level::FailureNote => true,
Level::Warning | Level::Note | Level::Help | Level::Allow => false,
Level::Warning | Level::Note | Level::Help | Level::Allow | Level::Expect(_) => false,
}
}

View File

@ -20,11 +20,12 @@ extern crate tracing;
pub use emitter::ColorConfig;
use rustc_lint_defs::LintExpectationId;
use Level::*;
use emitter::{is_case_difference, Emitter, EmitterWriter};
use registry::Registry;
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
use rustc_data_structures::stable_hasher::StableHasher;
use rustc_data_structures::sync::{self, Lock, Lrc};
use rustc_data_structures::AtomicRef;
@ -450,6 +451,22 @@ struct HandlerInner {
deduplicated_warn_count: usize,
future_breakage_diagnostics: Vec<Diagnostic>,
/// Expected [`Diagnostic`]s store a [`LintExpectationId`] as part of
/// the lint level. [`LintExpectationId`]s created early during the compilation
/// (before `HirId`s have been defined) are not stable and can therefore not be
/// stored on disk. This buffer stores these diagnostics until the ID has been
/// replaced by a stable [`LintExpectationId`]. The [`Diagnostic`]s are the
/// submitted for storage and added to the list of fulfilled expectations.
unstable_expect_diagnostics: Vec<Diagnostic>,
/// expected diagnostic will have the level `Expect` which additionally
/// carries the [`LintExpectationId`] of the expectation that can be
/// marked as fulfilled. This is a collection of all [`LintExpectationId`]s
/// that have been marked as fulfilled this way.
///
/// [RFC-2383]: https://rust-lang.github.io/rfcs/2383-lint-reasons.html
fulfilled_expectations: FxHashSet<LintExpectationId>,
}
/// A key denoting where from a diagnostic was stashed.
@ -570,6 +587,8 @@ impl Handler {
emitted_diagnostics: Default::default(),
stashed_diagnostics: Default::default(),
future_breakage_diagnostics: Vec::new(),
unstable_expect_diagnostics: Vec::new(),
fulfilled_expectations: Default::default(),
}),
}
}
@ -677,6 +696,11 @@ impl Handler {
DiagnosticBuilder::new(self, Level::Allow, msg)
}
/// Construct a builder at the `Expect` level with the `msg`.
pub fn struct_expect(&self, msg: &str, id: LintExpectationId) -> DiagnosticBuilder<'_, ()> {
DiagnosticBuilder::new(self, Level::Expect(id), msg)
}
/// Construct a builder at the `Error` level at the given `span` and with the `msg`.
pub fn struct_span_err(
&self,
@ -906,6 +930,48 @@ impl Handler {
pub fn emit_unused_externs(&self, lint_level: &str, unused_externs: &[&str]) {
self.inner.borrow_mut().emit_unused_externs(lint_level, unused_externs)
}
pub fn update_unstable_expectation_id(
&self,
unstable_to_stable: &FxHashMap<LintExpectationId, LintExpectationId>,
) {
let diags = std::mem::take(&mut self.inner.borrow_mut().unstable_expect_diagnostics);
if diags.is_empty() {
return;
}
let mut inner = self.inner.borrow_mut();
for mut diag in diags.into_iter() {
let mut unstable_id = diag
.level
.get_expectation_id()
.expect("all diagnostics inside `unstable_expect_diagnostics` must have a `LintExpectationId`");
// The unstable to stable map only maps the unstable `AttrId` to a stable `HirId` with an attribute index.
// The lint index inside the attribute is manually transferred here.
let lint_index = unstable_id.get_lint_index();
unstable_id.set_lint_index(None);
let mut stable_id = *unstable_to_stable
.get(&unstable_id)
.expect("each unstable `LintExpectationId` must have a matching stable id");
stable_id.set_lint_index(lint_index);
diag.level = Level::Expect(stable_id);
inner.fulfilled_expectations.insert(stable_id);
(*TRACK_DIAGNOSTICS)(&diag);
}
}
/// This methods steals all [`LintExpectationId`]s that are stored inside
/// [`HandlerInner`] and indicate that the linked expectation has been fulfilled.
pub fn steal_fulfilled_expectation_ids(&self) -> FxHashSet<LintExpectationId> {
assert!(
self.inner.borrow().unstable_expect_diagnostics.is_empty(),
"`HandlerInner::unstable_expect_diagnostics` should be empty at this point",
);
std::mem::take(&mut self.inner.borrow_mut().fulfilled_expectations)
}
}
impl HandlerInner {
@ -951,9 +1017,21 @@ impl HandlerInner {
return;
}
// The `LintExpectationId` can be stable or unstable depending on when it was created.
// Diagnostics created before the definition of `HirId`s are unstable and can not yet
// be stored. Instead, they are buffered until the `LintExpectationId` is replaced by
// a stable one by the `LintLevelsBuilder`.
if let Level::Expect(LintExpectationId::Unstable { .. }) = diagnostic.level {
self.unstable_expect_diagnostics.push(diagnostic.clone());
return;
}
(*TRACK_DIAGNOSTICS)(diagnostic);
if diagnostic.level == Allow {
if let Level::Expect(expectation_id) = diagnostic.level {
self.fulfilled_expectations.insert(expectation_id);
return;
} else if diagnostic.level == Allow {
return;
}
@ -1250,6 +1328,7 @@ pub enum Level {
Help,
FailureNote,
Allow,
Expect(LintExpectationId),
}
impl fmt::Display for Level {
@ -1275,7 +1354,7 @@ impl Level {
spec.set_fg(Some(Color::Cyan)).set_intense(true);
}
FailureNote => {}
Allow => unreachable!(),
Allow | Expect(_) => unreachable!(),
}
spec
}
@ -1289,12 +1368,20 @@ impl Level {
Help => "help",
FailureNote => "failure-note",
Allow => panic!("Shouldn't call on allowed error"),
Expect(_) => panic!("Shouldn't call on expected error"),
}
}
pub fn is_failure_note(&self) -> bool {
matches!(*self, FailureNote)
}
pub fn get_expectation_id(&self) -> Option<LintExpectationId> {
match self {
Level::Expect(id) => Some(*id),
_ => None,
}
}
}
// FIXME(eddyb) this doesn't belong here AFAICT, should be moved to callsite.

View File

@ -282,6 +282,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
ungated!(
allow, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk
),
gated!(
expect, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk,
lint_reasons, experimental!(expect)
),
ungated!(
forbid, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk
),

View File

@ -109,6 +109,7 @@ struct LintGroup {
depr: Option<LintAlias>,
}
#[derive(Debug)]
pub enum CheckLintNameResult<'a> {
Ok(&'a [LintId]),
/// Lint doesn't exist. Potentially contains a suggestion for a correct lint name.
@ -377,6 +378,9 @@ impl LintStore {
Level::ForceWarn => "--force-warn",
Level::Deny => "-D",
Level::Forbid => "-F",
Level::Expect(_) => {
unreachable!("lints with the level of `expect` should not run this code");
}
},
lint_name
);

View File

@ -59,7 +59,8 @@ impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> {
F: FnOnce(&mut Self),
{
let is_crate_node = id == ast::CRATE_NODE_ID;
let push = self.context.builder.push(attrs, is_crate_node);
let push = self.context.builder.push(attrs, is_crate_node, None);
self.check_id(id);
self.enter_attrs(attrs);
f(self);

View File

@ -0,0 +1,49 @@
use crate::builtin;
use rustc_hir::HirId;
use rustc_middle::{lint::LintExpectation, ty::TyCtxt};
use rustc_session::lint::LintExpectationId;
use rustc_span::symbol::sym;
pub fn check_expectations(tcx: TyCtxt<'_>) {
if !tcx.sess.features_untracked().enabled(sym::lint_reasons) {
return;
}
let fulfilled_expectations = tcx.sess.diagnostic().steal_fulfilled_expectation_ids();
let lint_expectations = &tcx.lint_levels(()).lint_expectations;
for (id, expectation) in lint_expectations {
if !fulfilled_expectations.contains(id) {
// This check will always be true, since `lint_expectations` only
// holds stable ids
if let LintExpectationId::Stable { hir_id, .. } = id {
emit_unfulfilled_expectation_lint(tcx, *hir_id, expectation);
} else {
unreachable!("at this stage all `LintExpectationId`s are stable");
}
}
}
}
fn emit_unfulfilled_expectation_lint(
tcx: TyCtxt<'_>,
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,
expectation.emission_span,
|diag| {
let mut diag = diag.build("this lint expectation is unfulfilled");
if let Some(rationale) = expectation.reason {
diag.note(&rationale.as_str());
}
diag.emit();
},
);
}

View File

@ -503,4 +503,7 @@ pub fn check_crate<'tcx, T: LateLintPass<'tcx>>(
});
},
);
// This check has to be run after all lints are done processing for this crate
tcx.sess.time("check_lint_expectations", || crate::expect::check_expectations(tcx));
}

View File

@ -7,17 +7,15 @@ use rustc_errors::{struct_span_err, Applicability, Diagnostic};
use rustc_hir as hir;
use rustc_hir::{intravisit, HirId};
use rustc_middle::hir::nested_filter;
use rustc_middle::lint::LevelAndSource;
use rustc_middle::lint::LintDiagnosticBuilder;
use rustc_middle::lint::{
struct_lint_level, LintLevelMap, LintLevelSets, LintLevelSource, LintSet, LintStackIndex,
COMMAND_LINE,
struct_lint_level, LevelAndSource, LintDiagnosticBuilder, LintExpectation, LintLevelMap,
LintLevelSets, LintLevelSource, LintSet, LintStackIndex, COMMAND_LINE,
};
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::{RegisteredTools, TyCtxt};
use rustc_session::lint::{
builtin::{self, FORBIDDEN_LINT_GROUPS},
Level, Lint, LintId,
Level, Lint, LintExpectationId, LintId,
};
use rustc_session::parse::feature_err;
use rustc_session::Session;
@ -34,16 +32,23 @@ fn lint_levels(tcx: TyCtxt<'_>, (): ()) -> LintLevelMap {
builder.levels.id_to_set.reserve(krate.owners.len() + 1);
let push = builder.levels.push(tcx.hir().attrs(hir::CRATE_HIR_ID), true);
let push =
builder.levels.push(tcx.hir().attrs(hir::CRATE_HIR_ID), true, Some(hir::CRATE_HIR_ID));
builder.levels.register_id(hir::CRATE_HIR_ID);
tcx.hir().walk_toplevel_module(&mut builder);
builder.levels.pop(push);
builder.levels.update_unstable_expectation_ids();
builder.levels.build_map()
}
pub struct LintLevelsBuilder<'s> {
sess: &'s Session,
lint_expectations: Vec<(LintExpectationId, LintExpectation)>,
/// Each expectation has a stable and an unstable identifier. This map
/// is used to map from unstable to stable [`LintExpectationId`]s.
expectation_id_map: FxHashMap<LintExpectationId, LintExpectationId>,
sets: LintLevelSets,
id_to_set: FxHashMap<HirId, LintStackIndex>,
cur: LintStackIndex,
@ -66,6 +71,8 @@ impl<'s> LintLevelsBuilder<'s> {
) -> Self {
let mut builder = LintLevelsBuilder {
sess,
lint_expectations: Default::default(),
expectation_id_map: Default::default(),
sets: LintLevelSets::new(),
cur: COMMAND_LINE,
id_to_set: Default::default(),
@ -226,13 +233,24 @@ impl<'s> LintLevelsBuilder<'s> {
/// `#[allow]`
///
/// Don't forget to call `pop`!
pub(crate) fn push(&mut self, attrs: &[ast::Attribute], is_crate_node: bool) -> BuilderPush {
pub(crate) fn push(
&mut self,
attrs: &[ast::Attribute],
is_crate_node: bool,
source_hir_id: Option<HirId>,
) -> BuilderPush {
let mut specs = FxHashMap::default();
let sess = self.sess;
let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input");
for attr in attrs {
let Some(level) = Level::from_symbol(attr.name_or_empty()) else {
continue
for (attr_index, attr) in attrs.iter().enumerate() {
let level = match Level::from_attr(attr) {
None => continue,
Some(Level::Expect(unstable_id)) if let Some(hir_id) = source_hir_id => {
let stable_id = self.create_stable_id(unstable_id, hir_id, attr_index);
Level::Expect(stable_id)
}
Some(lvl) => lvl,
};
let Some(mut metas) = attr.meta_item_list() else {
@ -285,9 +303,17 @@ impl<'s> LintLevelsBuilder<'s> {
}
}
for li in metas {
for (lint_index, li) in metas.iter_mut().enumerate() {
let level = match level {
Level::Expect(mut id) => {
id.set_lint_index(Some(lint_index as u16));
Level::Expect(id)
}
level => level,
};
let sp = li.span();
let mut meta_item = match li {
let meta_item = match li {
ast::NestedMetaItem::MetaItem(meta_item) if meta_item.is_word() => meta_item,
_ => {
let mut err = bad_attr(sp);
@ -327,6 +353,10 @@ impl<'s> LintLevelsBuilder<'s> {
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) => {
@ -342,6 +372,10 @@ impl<'s> LintLevelsBuilder<'s> {
for id in ids {
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)));
}
}
Err((Some(ids), ref new_lint_name)) => {
let lint = builtin::RENAMED_AND_REMOVED_LINTS;
@ -378,6 +412,10 @@ impl<'s> LintLevelsBuilder<'s> {
for id in ids {
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)));
}
}
Err((None, _)) => {
// If Tool(Err(None, _)) is returned, then either the lint does not
@ -471,6 +509,10 @@ impl<'s> LintLevelsBuilder<'s> {
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)));
}
} else {
panic!("renamed lint does not exist: {}", new_name);
}
@ -519,6 +561,20 @@ impl<'s> LintLevelsBuilder<'s> {
BuilderPush { prev, changed: prev != self.cur }
}
fn create_stable_id(
&mut self,
unstable_id: LintExpectationId,
hir_id: HirId,
attr_index: usize,
) -> LintExpectationId {
let stable_id =
LintExpectationId::Stable { hir_id, attr_index: attr_index as u16, lint_index: None };
self.expectation_id_map.insert(unstable_id, stable_id);
stable_id
}
/// Checks if the lint is gated on a feature that is not enabled.
fn check_gated_lint(&self, lint_id: LintId, span: Span) {
if let Some(feature) = lint_id.lint.feature_gate {
@ -562,8 +618,16 @@ impl<'s> LintLevelsBuilder<'s> {
self.id_to_set.insert(id, self.cur);
}
fn update_unstable_expectation_ids(&self) {
self.sess.diagnostic().update_unstable_expectation_id(&self.expectation_id_map);
}
pub fn build_map(self) -> LintLevelMap {
LintLevelMap { sets: self.sets, id_to_set: self.id_to_set }
LintLevelMap {
sets: self.sets,
id_to_set: self.id_to_set,
lint_expectations: self.lint_expectations,
}
}
}
@ -579,7 +643,8 @@ impl LintLevelMapBuilder<'_> {
{
let is_crate_hir = id == hir::CRATE_HIR_ID;
let attrs = self.tcx.hir().attrs(id);
let push = self.levels.push(attrs, is_crate_hir);
let push = self.levels.push(attrs, is_crate_hir, Some(id));
if push.changed {
self.levels.register_id(id);
}

View File

@ -51,6 +51,7 @@ pub mod builtin;
mod context;
mod early;
mod enum_intrinsics_non_enums;
mod expect;
pub mod hidden_unicode_codepoints;
mod internal;
mod late;

View File

@ -10,3 +10,4 @@ rustc_span = { path = "../rustc_span" }
rustc_serialize = { path = "../rustc_serialize" }
rustc_macros = { path = "../rustc_macros" }
rustc_target = { path = "../rustc_target" }
rustc_hir = { path = "../rustc_hir" }

View File

@ -495,6 +495,39 @@ declare_lint! {
"unrecognized lint attribute"
}
declare_lint! {
/// The `unfulfilled_lint_expectations` lint detects lint trigger expectations
/// that have not been fulfilled.
///
/// ### Example
///
/// ```rust
/// #![feature(lint_reasons)]
///
/// #[expect(unused_variables)]
/// let x = 10;
/// println!("{}", x);
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// It was expected that the marked code would emit a lint. This expectation
/// has not been fulfilled.
///
/// The `expect` attribute can be removed if this is intended behavior otherwise
/// it should be investigated why the expected lint is no longer issued.
///
/// Part of RFC 2383. The progress is being tracked in [#54503]
///
/// [#54503]: https://github.com/rust-lang/rust/issues/54503
pub UNFULFILLED_LINT_EXPECTATIONS,
Warn,
"unfulfilled lint expectation",
@feature_gate = rustc_span::sym::lint_reasons;
}
declare_lint! {
/// The `unused_variables` lint detects variables which are not used in
/// any way.
@ -3007,6 +3040,7 @@ declare_lint_pass! {
UNUSED_CRATE_DEPENDENCIES,
UNUSED_QUALIFICATIONS,
UNKNOWN_LINTS,
UNFULFILLED_LINT_EXPECTATIONS,
UNUSED_VARIABLES,
UNUSED_ASSIGNMENTS,
DEAD_CODE,

View File

@ -1,9 +1,13 @@
#![feature(min_specialization)]
#[macro_use]
extern crate rustc_macros;
pub use self::Level::*;
use rustc_ast::node_id::{NodeId, NodeMap};
use rustc_ast::{AttrId, Attribute};
use rustc_data_structures::stable_hasher::{HashStable, StableHasher, ToStableHashKey};
use rustc_hir::HirId;
use rustc_serialize::json::Json;
use rustc_span::edition::Edition;
use rustc_span::{sym, symbol::Ident, MultiSpan, Span, Symbol};
@ -46,13 +50,119 @@ pub enum Applicability {
Unspecified,
}
/// Each lint expectation has a `LintExpectationId` assigned by the `LintLevelsBuilder`.
/// Expected `Diagnostic`s get the lint level `Expect` which stores the `LintExpectationId`
/// to match it with the actual expectation later on.
///
/// The `LintExpectationId` has to be has stable between compilations, as diagnostic
/// instances might be loaded from cache. Lint messages can be emitted during an
/// `EarlyLintPass` operating on the AST and during a `LateLintPass` traversing the
/// HIR tree. The AST doesn't have enough information to create a stable id. The
/// `LintExpectationId` will instead store the [`AttrId`] defining the expectation.
/// These `LintExpectationId` will be updated to use the stable [`HirId`] once the
/// AST has been lowered. The transformation is done by the `LintLevelsBuilder`
///
/// Each lint inside the `expect` attribute is tracked individually, the `lint_index`
/// identifies the lint inside the attribute and ensures that the IDs are unique.
///
/// The index values have a type of `u16` to reduce the size of the `LintExpectationId`.
/// It's reasonable to assume that no user will define 2^16 attributes on one node or
/// have that amount of lints listed. `u16` values should therefore suffice.
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Encodable, Decodable)]
pub enum LintExpectationId {
/// Used for lints emitted during the `EarlyLintPass`. This id is not
/// has stable and should not be cached.
Unstable { attr_id: AttrId, lint_index: Option<u16> },
/// The [`HirId`] that the lint expectation is attached to. This id is
/// stable and can be cached. The additional index ensures that nodes with
/// several expectations can correctly match diagnostics to the individual
/// expectation.
Stable { hir_id: HirId, attr_index: u16, lint_index: Option<u16> },
}
impl LintExpectationId {
pub fn is_stable(&self) -> bool {
match self {
LintExpectationId::Unstable { .. } => false,
LintExpectationId::Stable { .. } => true,
}
}
pub fn get_lint_index(&self) -> Option<u16> {
let (LintExpectationId::Unstable { lint_index, .. }
| LintExpectationId::Stable { lint_index, .. }) = self;
*lint_index
}
pub fn set_lint_index(&mut self, new_lint_index: Option<u16>) {
let (LintExpectationId::Unstable { ref mut lint_index, .. }
| LintExpectationId::Stable { ref mut lint_index, .. }) = self;
*lint_index = new_lint_index
}
}
impl<HCX: rustc_hir::HashStableContext> HashStable<HCX> for LintExpectationId {
#[inline]
fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
match self {
LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => {
hir_id.hash_stable(hcx, hasher);
attr_index.hash_stable(hcx, hasher);
lint_index.hash_stable(hcx, hasher);
}
_ => {
unreachable!("HashStable should only be called for a filled `LintExpectationId`")
}
}
}
}
impl<HCX: rustc_hir::HashStableContext> ToStableHashKey<HCX> for LintExpectationId {
type KeyType = (HirId, u16, u16);
#[inline]
fn to_stable_hash_key(&self, _: &HCX) -> Self::KeyType {
match self {
LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => {
(*hir_id, *attr_index, *lint_index)
}
_ => {
unreachable!("HashStable should only be called for a filled `LintExpectationId`")
}
}
}
}
/// Setting for how to handle a lint.
///
/// See: <https://doc.rust-lang.org/rustc/lints/levels.html>
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
pub enum Level {
/// The `allow` level will not issue any message.
Allow,
/// The `expect` level will suppress the lint message but in turn produce a message
/// if the lint wasn't issued in the expected scope. `Expect` should not be used as
/// an initial level for a lint.
///
/// Note that this still means that the lint is enabled in this position and should
/// be emitted, this will in turn fulfill the expectation and suppress the lint.
///
/// See RFC 2383.
///
/// The `LintExpectationId` is used to later link a lint emission to the actual
/// expectation. It can be ignored in most cases.
Expect(LintExpectationId),
/// The `warn` level will produce a warning if the lint was violated, however the
/// compiler will continue with its execution.
Warn,
ForceWarn,
/// The `deny` level will produce an error and stop further execution after the lint
/// pass is complete.
Deny,
/// `Forbid` is equivalent to the `deny` level but can't be overwritten like the previous
/// levels.
Forbid,
}
@ -63,6 +173,7 @@ impl Level {
pub fn as_str(self) -> &'static str {
match self {
Level::Allow => "allow",
Level::Expect(_) => "expect",
Level::Warn => "warn",
Level::ForceWarn => "force-warn",
Level::Deny => "deny",
@ -70,21 +181,26 @@ impl Level {
}
}
/// Converts a lower-case string to a level.
/// Converts a lower-case string to a level. This will never construct the expect
/// level as that would require a [`LintExpectationId`]
pub fn from_str(x: &str) -> Option<Level> {
match x {
"allow" => Some(Level::Allow),
"warn" => Some(Level::Warn),
"deny" => Some(Level::Deny),
"forbid" => Some(Level::Forbid),
_ => None,
"expect" | _ => None,
}
}
/// Converts a symbol to a level.
pub fn from_symbol(x: Symbol) -> Option<Level> {
match x {
pub fn from_attr(attr: &Attribute) -> Option<Level> {
match attr.name_or_empty() {
sym::allow => Some(Level::Allow),
sym::expect => Some(Level::Expect(LintExpectationId::Unstable {
attr_id: attr.id,
lint_index: None,
})),
sym::warn => Some(Level::Warn),
sym::deny => Some(Level::Deny),
sym::forbid => Some(Level::Forbid),

View File

@ -8,7 +8,7 @@ use rustc_index::vec::IndexVec;
use rustc_query_system::ich::StableHashingContext;
use rustc_session::lint::{
builtin::{self, FORBIDDEN_LINT_GROUPS},
FutureIncompatibilityReason, Level, Lint, LintId,
FutureIncompatibilityReason, Level, Lint, LintExpectationId, LintId,
};
use rustc_session::{DiagnosticMessageId, Session};
use rustc_span::hygiene::MacroKind;
@ -153,6 +153,13 @@ impl LintLevelSets {
#[derive(Debug)]
pub struct LintLevelMap {
/// This is a collection of lint expectations as described in RFC 2383, that
/// can be fulfilled during this compilation session. This means that at least
/// one expected lint is currently registered in the lint store.
///
/// The [`LintExpectationId`] is stored as a part of the [`Expect`](Level::Expect)
/// lint level.
pub lint_expectations: Vec<(LintExpectationId, LintExpectation)>,
pub sets: LintLevelSets,
pub id_to_set: FxHashMap<HirId, LintStackIndex>,
}
@ -178,14 +185,33 @@ impl LintLevelMap {
impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
#[inline]
fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
let LintLevelMap { ref sets, ref id_to_set } = *self;
let LintLevelMap { ref sets, ref id_to_set, ref lint_expectations } = *self;
id_to_set.hash_stable(hcx, hasher);
lint_expectations.hash_stable(hcx, hasher);
hcx.while_hashing_spans(true, |hcx| sets.hash_stable(hcx, hasher))
}
}
/// This struct represents a lint expectation and holds all required information
/// to emit the `unfulfilled_lint_expectations` lint if it is unfulfilled after
/// the `LateLintPass` has completed.
#[derive(Clone, Debug, HashStable)]
pub struct LintExpectation {
/// The reason for this expectation that can optionally be added as part of
/// the attribute. It will be displayed as part of the lint message.
pub reason: Option<Symbol>,
/// The [`Span`] of the attribute that this expectation originated from.
pub emission_span: Span,
}
impl LintExpectation {
pub fn new(reason: Option<Symbol>, attr_span: Span) -> Self {
Self { reason, emission_span: attr_span }
}
}
pub struct LintDiagnosticBuilder<'a>(DiagnosticBuilder<'a, ()>);
impl<'a> LintDiagnosticBuilder<'a> {
@ -225,6 +251,9 @@ pub fn explain_lint_level_source(
Level::Forbid => "-F",
Level::Allow => "-A",
Level::ForceWarn => "--force-warn",
Level::Expect(_) => {
unreachable!("the expect level does not have a commandline flag")
}
};
let hyphen_case_lint_name = name.replace('_', "-");
if lint_flag_val.as_str() == name {
@ -314,6 +343,16 @@ pub fn struct_lint_level<'s, 'd>(
return;
}
}
(Level::Expect(expect_id), _) => {
// This case is special as we actually allow the lint itself in this context, but
// we can't return early like in the case for `Level::Allow` because we still
// need the lint diagnostic to be emitted to `rustc_error::HanderInner`.
//
// We can also not mark the lint expectation as fulfilled here right away, as it
// can still be cancelled in the decorate function. All of this means that we simply
// create a `DiagnosticBuilder` and continue as we would for warnings.
sess.struct_expect("", expect_id)
}
(Level::Warn | Level::ForceWarn, Some(span)) => sess.struct_span_warn(span, ""),
(Level::Warn | Level::ForceWarn, None) => sess.struct_warn(""),
(Level::Deny | Level::Forbid, Some(span)) => {
@ -346,6 +385,17 @@ pub fn struct_lint_level<'s, 'd>(
}
}
// Lint diagnostics that are covered by the expect level will not be emitted outside
// the compiler. It is therefore not necessary to add any information for the user.
// This will therefore directly call the decorate function which will in turn emit
// the `Diagnostic`.
if let Level::Expect(_) = level {
let name = lint.name_lower();
err.code(DiagnosticId::Lint { name, has_future_breakage, is_force_warn: false });
decorate(LintDiagnosticBuilder::new(err));
return;
}
explain_lint_level_source(sess, lint, level, src, &mut err);
let name = lint.name_lower();

View File

@ -2755,7 +2755,7 @@ impl<'tcx> TyCtxt<'tcx> {
return bound;
}
if hir.attrs(id).iter().any(|attr| Level::from_symbol(attr.name_or_empty()).is_some()) {
if hir.attrs(id).iter().any(|attr| Level::from_attr(attr).is_some()) {
return id;
}
let next = hir.get_parent_node(id);

View File

@ -331,6 +331,13 @@ impl Session {
pub fn struct_allow(&self, msg: &str) -> DiagnosticBuilder<'_, ()> {
self.diagnostic().struct_allow(msg)
}
pub fn struct_expect(
&self,
msg: &str,
id: lint::LintExpectationId,
) -> DiagnosticBuilder<'_, ()> {
self.diagnostic().struct_expect(msg, id)
}
pub fn struct_span_err<S: Into<MultiSpan>>(
&self,
sp: S,

View File

@ -0,0 +1,45 @@
// check-pass
#![feature(lint_reasons)]
#![warn(unused)]
// This expect attribute should catch all lint triggers
#[expect(unused_variables)]
fn check_multiple_lints_1() {
let value_i = 0xff00ff;
let value_ii = 0xff00ff;
let value_iii = 0xff00ff;
let value_iiii = 0xff00ff;
let value_iiiii = 0xff00ff;
}
// This expect attribute should catch all lint triggers
#[expect(unused_mut)]
fn check_multiple_lints_2() {
let mut a = 0xa;
let mut b = 0xb;
let mut c = 0xc;
println!("The ABC goes as: {:#x} {:#x} {:#x}", a, b, c);
}
// This expect attribute should catch all lint triggers
#[expect(while_true)]
fn check_multiple_lints_3() {
// `while_true` is an early lint
while true {}
while true {}
while true {}
while true {}
while true {}
}
fn main() {
check_multiple_lints_1();
check_multiple_lints_2();
check_multiple_lints_3();
}

View File

@ -0,0 +1,15 @@
// check-pass
#![feature(lint_reasons)]
#![warn(unused)]
#![expect(unused_mut)]
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
//~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default
#![expect(unused_variables)]
fn main() {
let x = 0;
}

View File

@ -0,0 +1,10 @@
warning: this lint expectation is unfulfilled
--> $DIR/crate_level_expect.rs:7:11
|
LL | #![expect(unused_mut)]
| ^^^^^^^^^^
|
= note: `#[warn(unfulfilled_lint_expectations)]` on by default
warning: 1 warning emitted

View File

@ -0,0 +1,16 @@
// check-pass
#![feature(lint_reasons)]
#![warn(unused)]
macro_rules! expect_inside_macro {
() => {
#[expect(unused_variables)]
let x = 0;
};
}
fn main() {
expect_inside_macro!();
}

View File

@ -0,0 +1,42 @@
// check-pass
#![feature(lint_reasons)]
#![warn(unused_variables)]
macro_rules! trigger_unused_variables_macro {
() => {
let x = 0;
//~^ WARNING unused variable: `x` [unused_variables]
//~| WARNING unused variable: `x` [unused_variables]
};
}
pub fn check_macro() {
// This should trigger the `unused_variables` from inside the macro
trigger_unused_variables_macro!();
}
// This should be fulfilled by the macro
#[expect(unused_variables)]
pub fn check_expect_on_item() {
trigger_unused_variables_macro!();
}
pub fn check_expect_on_macro() {
// This should be fulfilled by the macro
#[expect(unused_variables)]
trigger_unused_variables_macro!();
// FIXME: Lint attributes currently don't work directly on macros, and
// therefore also doesn't work for the new `expect` attribute. This bug
// is being tracked in rust#87391. The test will until then produce two
// warnings about the unused variable x.
//
// The expectation is still marked as fulfilled. I'm not totally why but
// my guess is that this will remain working when rust#87391 has been fixed.
}
fn main() {
}

View File

@ -0,0 +1,29 @@
warning: unused variable: `x`
--> $DIR/expect_lint_from_macro.rs:9:13
|
LL | let x = 0;
| ^ help: if this is intentional, prefix it with an underscore: `_x`
...
LL | trigger_unused_variables_macro!();
| --------------------------------- in this macro invocation
|
note: the lint level is defined here
--> $DIR/expect_lint_from_macro.rs:5:9
|
LL | #![warn(unused_variables)]
| ^^^^^^^^^^^^^^^^
= note: this warning originates in the macro `trigger_unused_variables_macro` (in Nightly builds, run with -Z macro-backtrace for more info)
warning: unused variable: `x`
--> $DIR/expect_lint_from_macro.rs:9:13
|
LL | let x = 0;
| ^ help: if this is intentional, prefix it with an underscore: `_x`
...
LL | trigger_unused_variables_macro!();
| --------------------------------- in this macro invocation
|
= note: this warning originates in the macro `trigger_unused_variables_macro` (in Nightly builds, run with -Z macro-backtrace for more info)
warning: 2 warnings emitted

View File

@ -0,0 +1,9 @@
// should error due to missing feature gate.
#![warn(unused)]
#[expect(unused)]
//~^ ERROR: the `#[expect]` attribute is an experimental feature [E0658]
fn main() {
let x = 1;
}

View File

@ -0,0 +1,12 @@
error[E0658]: the `#[expect]` attribute is an experimental feature
--> $DIR/expect_missing_feature_gate.rs:5:1
|
LL | #[expect(unused)]
| ^^^^^^^^^^^^^^^^^
|
= note: see issue #54503 <https://github.com/rust-lang/rust/issues/54503> for more information
= help: add `#![feature(lint_reasons)]` to the crate attributes to enable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0658`.

View File

@ -0,0 +1,58 @@
// check-pass
#![feature(lint_reasons)]
#![warn(unused)]
// The warnings are not double triggers, they identify different unfulfilled lint
// expectations one for each listed lint.
#[expect(unused_variables, unused_mut, while_true)]
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
//~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default
//~| WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
fn check_multiple_lints_1() {
// This only trigger `unused_variables`
let who_am_i = 666;
}
#[expect(unused_variables, unused_mut, while_true)]
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
//~| WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
fn check_multiple_lints_2() {
// This only triggers `unused_mut`
let mut x = 0;
println!("I use x: {}", x);
}
#[expect(unused_variables, unused_mut, while_true)]
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
//~| WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
fn check_multiple_lints_3() {
// This only triggers `while_true` which is also an early lint
while true {}
}
#[expect(unused, while_true)]
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
fn check_multiple_lints_with_lint_group_1() {
let who_am_i = 666;
let mut x = 0;
println!("I use x: {}", x);
}
#[expect(unused, while_true)]
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
fn check_multiple_lints_with_lint_group_2() {
while true {}
}
fn main() {
check_multiple_lints_1();
check_multiple_lints_2();
check_multiple_lints_3();
check_multiple_lints_with_lint_group_1();
check_multiple_lints_with_lint_group_2();
}

View File

@ -0,0 +1,52 @@
warning: this lint expectation is unfulfilled
--> $DIR/expect_multiple_lints.rs:10:28
|
LL | #[expect(unused_variables, unused_mut, while_true)]
| ^^^^^^^^^^
|
= note: `#[warn(unfulfilled_lint_expectations)]` on by default
warning: this lint expectation is unfulfilled
--> $DIR/expect_multiple_lints.rs:10:40
|
LL | #[expect(unused_variables, unused_mut, while_true)]
| ^^^^^^^^^^
warning: this lint expectation is unfulfilled
--> $DIR/expect_multiple_lints.rs:19:10
|
LL | #[expect(unused_variables, unused_mut, while_true)]
| ^^^^^^^^^^^^^^^^
warning: this lint expectation is unfulfilled
--> $DIR/expect_multiple_lints.rs:19:40
|
LL | #[expect(unused_variables, unused_mut, while_true)]
| ^^^^^^^^^^
warning: this lint expectation is unfulfilled
--> $DIR/expect_multiple_lints.rs:28:10
|
LL | #[expect(unused_variables, unused_mut, while_true)]
| ^^^^^^^^^^^^^^^^
warning: this lint expectation is unfulfilled
--> $DIR/expect_multiple_lints.rs:28:28
|
LL | #[expect(unused_variables, unused_mut, while_true)]
| ^^^^^^^^^^
warning: this lint expectation is unfulfilled
--> $DIR/expect_multiple_lints.rs:36:18
|
LL | #[expect(unused, while_true)]
| ^^^^^^^^^^
warning: this lint expectation is unfulfilled
--> $DIR/expect_multiple_lints.rs:45:10
|
LL | #[expect(unused, while_true)]
| ^^^^^^
warning: 8 warnings emitted

View File

@ -0,0 +1,53 @@
// ignore-tidy-linelength
#![feature(lint_reasons)]
#![warn(unused_mut)]
#[expect(
unused_mut,
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
//~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default
//~| NOTE this `expect` is overridden by a `allow` attribute before the `unused_mut` lint is triggered
reason = "this `expect` is overridden by a `allow` attribute before the `unused_mut` lint is triggered"
)]
mod foo {
fn bar() {
#[allow(
unused_mut,
reason = "this overrides the previous `expect` lint level and allows the `unused_mut` lint here"
)]
let mut v = 0;
}
}
#[expect(
unused_mut,
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
//~| NOTE this `expect` is overridden by a `warn` attribute before the `unused_mut` lint is triggered
reason = "this `expect` is overridden by a `warn` attribute before the `unused_mut` lint is triggered"
)]
mod oof {
#[warn(
unused_mut,
//~^ NOTE the lint level is defined here
reason = "this overrides the previous `expect` lint level and warns about the `unused_mut` lint here"
)]
fn bar() {
let mut v = 0;
//~^ WARNING variable does not need to be mutable [unused_mut]
//~| NOTE this overrides the previous `expect` lint level and warns about the `unused_mut` lint here
//~| HELP remove this `mut`
}
}
#[expect(unused_variables)]
//~^ WARNING this lint expectation is unfulfilled
#[forbid(unused_variables)]
//~^ NOTE the lint level is defined here
fn check_expect_then_forbid() {
let this_is_my_function = 3;
//~^ ERROR unused variable: `this_is_my_function` [unused_variables]
//~| HELP if this is intentional, prefix it with an underscore
}
fn main() {}

View File

@ -0,0 +1,52 @@
error: unused variable: `this_is_my_function`
--> $DIR/expect_nested_lint_levels.rs:48:9
|
LL | let this_is_my_function = 3;
| ^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_this_is_my_function`
|
note: the lint level is defined here
--> $DIR/expect_nested_lint_levels.rs:45:10
|
LL | #[forbid(unused_variables)]
| ^^^^^^^^^^^^^^^^
warning: variable does not need to be mutable
--> $DIR/expect_nested_lint_levels.rs:36:13
|
LL | let mut v = 0;
| ----^
| |
| help: remove this `mut`
|
= note: this overrides the previous `expect` lint level and warns about the `unused_mut` lint here
note: the lint level is defined here
--> $DIR/expect_nested_lint_levels.rs:31:9
|
LL | unused_mut,
| ^^^^^^^^^^
warning: this lint expectation is unfulfilled
--> $DIR/expect_nested_lint_levels.rs:7:5
|
LL | unused_mut,
| ^^^^^^^^^^
|
= note: `#[warn(unfulfilled_lint_expectations)]` on by default
= note: this `expect` is overridden by a `allow` attribute before the `unused_mut` lint is triggered
warning: this lint expectation is unfulfilled
--> $DIR/expect_nested_lint_levels.rs:24:5
|
LL | unused_mut,
| ^^^^^^^^^^
|
= note: this `expect` is overridden by a `warn` attribute before the `unused_mut` lint is triggered
warning: this lint expectation is unfulfilled
--> $DIR/expect_nested_lint_levels.rs:43:10
|
LL | #[expect(unused_variables)]
| ^^^^^^^^^^^^^^^^
error: aborting due to previous error; 4 warnings emitted

View File

@ -0,0 +1,34 @@
#![feature(lint_reasons)]
#[forbid(unused_variables)]
//~^ NOTE `forbid` level set here
//~| NOTE `forbid` level set here
#[expect(unused_variables)]
//~^ ERROR incompatible with previous forbid [E0453]
//~| NOTE overruled by previous forbid
//~| ERROR incompatible with previous forbid [E0453]
//~| NOTE overruled by previous forbid
fn expect_forbidden_lint_1() {}
#[forbid(while_true)]
//~^ NOTE `forbid` level set here
//~| NOTE `forbid` level set here
//~| NOTE the lint level is defined here
#[expect(while_true)]
//~^ ERROR incompatible with previous forbid [E0453]
//~| NOTE overruled by previous forbid
//~| ERROR incompatible with previous forbid [E0453]
//~| NOTE overruled by previous forbid
fn expect_forbidden_lint_2() {
// This while loop will produce a `while_true` lint as the lint level
// at this node is still `forbid` and the `while_true` check happens
// before the compilation terminates due to `E0453`
while true {}
//~^ ERROR denote infinite loops with `loop { ... }`
//~| HELP use `loop`
}
fn main() {
expect_forbidden_lint_1();
expect_forbidden_lint_2();
}

View File

@ -0,0 +1,51 @@
error[E0453]: expect(unused_variables) incompatible with previous forbid
--> $DIR/expect_with_forbid.rs:6:10
|
LL | #[forbid(unused_variables)]
| ---------------- `forbid` level set here
...
LL | #[expect(unused_variables)]
| ^^^^^^^^^^^^^^^^ overruled by previous forbid
error[E0453]: expect(while_true) incompatible with previous forbid
--> $DIR/expect_with_forbid.rs:17:10
|
LL | #[forbid(while_true)]
| ---------- `forbid` level set here
...
LL | #[expect(while_true)]
| ^^^^^^^^^^ overruled by previous forbid
error[E0453]: expect(unused_variables) incompatible with previous forbid
--> $DIR/expect_with_forbid.rs:6:10
|
LL | #[forbid(unused_variables)]
| ---------------- `forbid` level set here
...
LL | #[expect(unused_variables)]
| ^^^^^^^^^^^^^^^^ overruled by previous forbid
error[E0453]: expect(while_true) incompatible with previous forbid
--> $DIR/expect_with_forbid.rs:17:10
|
LL | #[forbid(while_true)]
| ---------- `forbid` level set here
...
LL | #[expect(while_true)]
| ^^^^^^^^^^ overruled by previous forbid
error: denote infinite loops with `loop { ... }`
--> $DIR/expect_with_forbid.rs:26:5
|
LL | while true {}
| ^^^^^^^^^^ help: use `loop`
|
note: the lint level is defined here
--> $DIR/expect_with_forbid.rs:13:10
|
LL | #[forbid(while_true)]
| ^^^^^^^^^^
error: aborting due to 5 previous errors
For more information about this error, try `rustc --explain E0453`.

View File

@ -0,0 +1,11 @@
// check-pass
#![feature(lint_reasons)]
#![warn(unused)]
#![expect(unused_variables, reason = "<This should fail and display this reason>")]
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
//~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default
//~| NOTE <This should fail and display this reason>
fn main() {}

View File

@ -0,0 +1,11 @@
warning: this lint expectation is unfulfilled
--> $DIR/expect_with_reason.rs:6:11
|
LL | #![expect(unused_variables, reason = "<This should fail and display this reason>")]
| ^^^^^^^^^^^^^^^^
|
= note: `#[warn(unfulfilled_lint_expectations)]` on by default
= note: <This should fail and display this reason>
warning: 1 warning emitted

View File

@ -0,0 +1,23 @@
// check-pass
#![feature(lint_reasons)]
fn expect_early_pass_lints() {
#[expect(while_true)]
while true {
println!("I never stop")
}
#[expect(unused_doc_comments)]
/// This comment triggers the `unused_doc_comments` lint
let _sheep = "wolf";
let x = 123;
#[expect(ellipsis_inclusive_range_patterns)]
match x {
0...100 => {}
_ => {}
}
}
fn main() {}

View File

@ -0,0 +1,43 @@
// check-pass
#![feature(lint_reasons)]
#![warn(unused)]
#[expect(unused_variables)]
fn check_specific_lint() {
let x = 2;
}
#[expect(unused)]
fn check_lint_group() {
let x = 15;
}
#[expect(unused_variables)]
fn check_multiple_lint_emissions() {
let r = 1;
let u = 8;
let s = 2;
let t = 9;
}
mod check_fulfilled_expect_in_macro {
macro_rules! expect_inside_macro {
() => {
#[expect(unused_variables)]
let x = 0;
};
}
pub fn check_macro() {
expect_inside_macro!();
}
}
fn main() {
check_specific_lint();
check_lint_group();
check_multiple_lint_emissions();
check_fulfilled_expect_in_macro::check_macro();
}

View File

@ -0,0 +1,14 @@
// check-pass
#![feature(lint_reasons)]
#![warn(unused)]
#[warn(unused_variables)]
#[expect(unused_variables)]
//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations]
//~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default
#[allow(unused_variables)]
#[expect(unused_variables)] // Only this expectation will be fulfilled
fn main() {
let x = 2;
}

View File

@ -0,0 +1,10 @@
warning: this lint expectation is unfulfilled
--> $DIR/multiple_expect_attrs.rs:7:10
|
LL | #[expect(unused_variables)]
| ^^^^^^^^^^^^^^^^
|
= note: `#[warn(unfulfilled_lint_expectations)]` on by default
warning: 1 warning emitted