diff --git a/Cargo.lock b/Cargo.lock index f0c6e371c38be..8ca6f26e326d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3882,6 +3882,7 @@ version = "0.0.0" dependencies = [ "rustc_ast", "rustc_data_structures", + "rustc_hir", "rustc_macros", "rustc_serialize", "rustc_span", diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs index 7d7ab1ed4e5e6..c380455012d0d 100644 --- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs +++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs @@ -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"), } } diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 6d6ada86428db..a59d91ea78900 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -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, } } diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index e45a57b26587a..217d3ec2c247a 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -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, + + /// 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, + + /// 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, } /// 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, + ) { + 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 { + 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 { + match self { + Level::Expect(id) => Some(*id), + _ => None, + } + } } // FIXME(eddyb) this doesn't belong here AFAICT, should be moved to callsite. diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 53762eef78592..4b9cf784495fe 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -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 ), diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index ad9a16fb39ae2..c34e9f1ba7831 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -109,6 +109,7 @@ struct LintGroup { depr: Option, } +#[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 ); diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index 1b2c88867d49d..e9b7620bf1d7f 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -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); diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs new file mode 100644 index 0000000000000..e6c9d0b0ab000 --- /dev/null +++ b/compiler/rustc_lint/src/expect.rs @@ -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(); + }, + ); +} diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index 0ce760b64d9ca..0ac636b878e0d 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -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)); } diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index d7cdb08d81714..f46f74fa45fb0 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -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, sets: LintLevelSets, id_to_set: FxHashMap, 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, + ) -> 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); } diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 72e1671449f1c..18f229564c2a3 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -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; diff --git a/compiler/rustc_lint_defs/Cargo.toml b/compiler/rustc_lint_defs/Cargo.toml index 798d50819e2d8..8acf7943de9cf 100644 --- a/compiler/rustc_lint_defs/Cargo.toml +++ b/compiler/rustc_lint_defs/Cargo.toml @@ -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" } diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index adec1a3ab00d6..272913f3f0e02 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -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, diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index e9c62fc400651..c78428765bb89 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -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 }, + /// 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 }, +} + +impl LintExpectationId { + pub fn is_stable(&self) -> bool { + match self { + LintExpectationId::Unstable { .. } => false, + LintExpectationId::Stable { .. } => true, + } + } + + pub fn get_lint_index(&self) -> Option { + let (LintExpectationId::Unstable { lint_index, .. } + | LintExpectationId::Stable { lint_index, .. }) = self; + + *lint_index + } + + pub fn set_lint_index(&mut self, new_lint_index: Option) { + let (LintExpectationId::Unstable { ref mut lint_index, .. } + | LintExpectationId::Stable { ref mut lint_index, .. }) = self; + + *lint_index = new_lint_index + } +} + +impl HashStable 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 ToStableHashKey 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: #[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 { 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 { - match x { + pub fn from_attr(attr: &Attribute) -> Option { + 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), diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs index 35e1558600d5b..894947fa70d96 100644 --- a/compiler/rustc_middle/src/lint.rs +++ b/compiler/rustc_middle/src/lint.rs @@ -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, } @@ -178,14 +185,33 @@ impl LintLevelMap { impl<'a> HashStable> 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, + /// The [`Span`] of the attribute that this expectation originated from. + pub emission_span: Span, +} + +impl LintExpectation { + pub fn new(reason: Option, 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(); diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index d32276364e0f9..8ce1b9d44257c 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -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); diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 360682558a5ef..eabebfcf3eae5 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -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>( &self, sp: S, diff --git a/src/test/ui/lint/rfc-2383-lint-reason/catch_multiple_lint_triggers.rs b/src/test/ui/lint/rfc-2383-lint-reason/catch_multiple_lint_triggers.rs new file mode 100644 index 0000000000000..6b255b799b7ec --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/catch_multiple_lint_triggers.rs @@ -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(); +} diff --git a/src/test/ui/lint/rfc-2383-lint-reason/crate_level_expect.rs b/src/test/ui/lint/rfc-2383-lint-reason/crate_level_expect.rs new file mode 100644 index 0000000000000..9f591ba985232 --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/crate_level_expect.rs @@ -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; +} diff --git a/src/test/ui/lint/rfc-2383-lint-reason/crate_level_expect.stderr b/src/test/ui/lint/rfc-2383-lint-reason/crate_level_expect.stderr new file mode 100644 index 0000000000000..7237f6fb6bba0 --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/crate_level_expect.stderr @@ -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 + diff --git a/src/test/ui/lint/rfc-2383-lint-reason/expect_inside_macro.rs b/src/test/ui/lint/rfc-2383-lint-reason/expect_inside_macro.rs new file mode 100644 index 0000000000000..b95815bc50b98 --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/expect_inside_macro.rs @@ -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!(); +} diff --git a/src/test/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.rs b/src/test/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.rs new file mode 100644 index 0000000000000..07c60fa0c325b --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.rs @@ -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() { + +} diff --git a/src/test/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr b/src/test/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr new file mode 100644 index 0000000000000..817e16fdcaa06 --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr @@ -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 + diff --git a/src/test/ui/lint/rfc-2383-lint-reason/expect_missing_feature_gate.rs b/src/test/ui/lint/rfc-2383-lint-reason/expect_missing_feature_gate.rs new file mode 100644 index 0000000000000..928e161061432 --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/expect_missing_feature_gate.rs @@ -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; +} diff --git a/src/test/ui/lint/rfc-2383-lint-reason/expect_missing_feature_gate.stderr b/src/test/ui/lint/rfc-2383-lint-reason/expect_missing_feature_gate.stderr new file mode 100644 index 0000000000000..b5601cf9e6594 --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/expect_missing_feature_gate.stderr @@ -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 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`. diff --git a/src/test/ui/lint/rfc-2383-lint-reason/expect_multiple_lints.rs b/src/test/ui/lint/rfc-2383-lint-reason/expect_multiple_lints.rs new file mode 100644 index 0000000000000..dc9a719a3f7e7 --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/expect_multiple_lints.rs @@ -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(); +} diff --git a/src/test/ui/lint/rfc-2383-lint-reason/expect_multiple_lints.stderr b/src/test/ui/lint/rfc-2383-lint-reason/expect_multiple_lints.stderr new file mode 100644 index 0000000000000..90ee744b26b01 --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/expect_multiple_lints.stderr @@ -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 + diff --git a/src/test/ui/lint/rfc-2383-lint-reason/expect_nested_lint_levels.rs b/src/test/ui/lint/rfc-2383-lint-reason/expect_nested_lint_levels.rs new file mode 100644 index 0000000000000..8f94bd6ec6cb8 --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/expect_nested_lint_levels.rs @@ -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() {} diff --git a/src/test/ui/lint/rfc-2383-lint-reason/expect_nested_lint_levels.stderr b/src/test/ui/lint/rfc-2383-lint-reason/expect_nested_lint_levels.stderr new file mode 100644 index 0000000000000..370e51bf70a7a --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/expect_nested_lint_levels.stderr @@ -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 + diff --git a/src/test/ui/lint/rfc-2383-lint-reason/expect_with_forbid.rs b/src/test/ui/lint/rfc-2383-lint-reason/expect_with_forbid.rs new file mode 100644 index 0000000000000..479ee198e1743 --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/expect_with_forbid.rs @@ -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(); +} diff --git a/src/test/ui/lint/rfc-2383-lint-reason/expect_with_forbid.stderr b/src/test/ui/lint/rfc-2383-lint-reason/expect_with_forbid.stderr new file mode 100644 index 0000000000000..a8116e93404f2 --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/expect_with_forbid.stderr @@ -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`. diff --git a/src/test/ui/lint/rfc-2383-lint-reason/expect_with_reason.rs b/src/test/ui/lint/rfc-2383-lint-reason/expect_with_reason.rs new file mode 100644 index 0000000000000..b4183d982118e --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/expect_with_reason.rs @@ -0,0 +1,11 @@ +// check-pass + +#![feature(lint_reasons)] +#![warn(unused)] + +#![expect(unused_variables, reason = "")] +//~^ WARNING this lint expectation is unfulfilled [unfulfilled_lint_expectations] +//~| NOTE `#[warn(unfulfilled_lint_expectations)]` on by default +//~| NOTE + +fn main() {} diff --git a/src/test/ui/lint/rfc-2383-lint-reason/expect_with_reason.stderr b/src/test/ui/lint/rfc-2383-lint-reason/expect_with_reason.stderr new file mode 100644 index 0000000000000..82c1a4c08bb39 --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/expect_with_reason.stderr @@ -0,0 +1,11 @@ +warning: this lint expectation is unfulfilled + --> $DIR/expect_with_reason.rs:6:11 + | +LL | #![expect(unused_variables, reason = "")] + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unfulfilled_lint_expectations)]` on by default + = note: + +warning: 1 warning emitted + diff --git a/src/test/ui/lint/rfc-2383-lint-reason/fulfilled_expectation_early_lints.rs b/src/test/ui/lint/rfc-2383-lint-reason/fulfilled_expectation_early_lints.rs new file mode 100644 index 0000000000000..6624b930e5e70 --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/fulfilled_expectation_early_lints.rs @@ -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() {} diff --git a/src/test/ui/lint/rfc-2383-lint-reason/fulfilled_expectation_late_lints.rs b/src/test/ui/lint/rfc-2383-lint-reason/fulfilled_expectation_late_lints.rs new file mode 100644 index 0000000000000..5d928b3cab3fe --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/fulfilled_expectation_late_lints.rs @@ -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(); +} diff --git a/src/test/ui/lint/rfc-2383-lint-reason/multiple_expect_attrs.rs b/src/test/ui/lint/rfc-2383-lint-reason/multiple_expect_attrs.rs new file mode 100644 index 0000000000000..98080b4e8224f --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/multiple_expect_attrs.rs @@ -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; +} diff --git a/src/test/ui/lint/rfc-2383-lint-reason/multiple_expect_attrs.stderr b/src/test/ui/lint/rfc-2383-lint-reason/multiple_expect_attrs.stderr new file mode 100644 index 0000000000000..df7d6584f99dd --- /dev/null +++ b/src/test/ui/lint/rfc-2383-lint-reason/multiple_expect_attrs.stderr @@ -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 +