Skip to content

Commit b6b897b

Browse files
committed
introduce future-compatibility warning for forbidden lint groups
We used to ignore `forbid(group)` scenarios completely. This changed in #78864, but that led to a number of regressions (#80988, #81218). This PR introduces a future compatibility warning for the case where a group is forbidden but then an individual lint within that group is allowed. We now issue a FCW when we see the "allow", but permit it to take effect.
1 parent c0b64d9 commit b6b897b

19 files changed

+554
-54
lines changed

compiler/rustc_lint/src/context.rs

+15
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use rustc_session::SessionLintStore;
3939
use rustc_span::lev_distance::find_best_match_for_name;
4040
use rustc_span::{symbol::Symbol, MultiSpan, Span, DUMMY_SP};
4141
use rustc_target::abi::LayoutOf;
42+
use tracing::debug;
4243

4344
use std::cell::Cell;
4445
use std::slice;
@@ -336,6 +337,20 @@ impl LintStore {
336337
}
337338
}
338339

340+
/// True if this symbol represents a lint group name.
341+
pub fn is_lint_group(&self, lint_name: Symbol) -> bool {
342+
debug!(
343+
"is_lint_group(lint_name={:?}, lint_groups={:?})",
344+
lint_name,
345+
self.lint_groups.keys().collect::<Vec<_>>()
346+
);
347+
let lint_name_str = &*lint_name.as_str();
348+
self.lint_groups.contains_key(&lint_name_str) || {
349+
let warnings_name_str = crate::WARNINGS.name_lower();
350+
lint_name_str == &*warnings_name_str
351+
}
352+
}
353+
339354
/// Checks the name of a lint for its existence, and whether it was
340355
/// renamed or removed. Generates a DiagnosticBuilder containing a
341356
/// warning for renamed and removed lints. This is over both lint

compiler/rustc_lint/src/levels.rs

+73-28
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use rustc_ast::attr;
55
use rustc_ast::unwrap_or;
66
use rustc_ast_pretty::pprust;
77
use rustc_data_structures::fx::FxHashMap;
8-
use rustc_errors::{struct_span_err, Applicability};
8+
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
99
use rustc_hir as hir;
1010
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
1111
use rustc_hir::{intravisit, HirId};
@@ -17,11 +17,15 @@ use rustc_middle::lint::{
1717
};
1818
use rustc_middle::ty::query::Providers;
1919
use rustc_middle::ty::TyCtxt;
20-
use rustc_session::lint::{builtin, Level, Lint, LintId};
20+
use rustc_session::lint::{
21+
builtin::{self, FORBIDDEN_LINT_GROUPS},
22+
Level, Lint, LintId,
23+
};
2124
use rustc_session::parse::feature_err;
2225
use rustc_session::Session;
2326
use rustc_span::symbol::{sym, Symbol};
2427
use rustc_span::{source_map::MultiSpan, Span, DUMMY_SP};
28+
use tracing::debug;
2529

2630
use std::cmp;
2731

@@ -51,6 +55,7 @@ pub struct LintLevelsBuilder<'s> {
5155
id_to_set: FxHashMap<HirId, u32>,
5256
cur: u32,
5357
warn_about_weird_lints: bool,
58+
store: &'s LintStore,
5459
}
5560

5661
pub struct BuilderPush {
@@ -59,13 +64,14 @@ pub struct BuilderPush {
5964
}
6065

6166
impl<'s> LintLevelsBuilder<'s> {
62-
pub fn new(sess: &'s Session, warn_about_weird_lints: bool, store: &LintStore) -> Self {
67+
pub fn new(sess: &'s Session, warn_about_weird_lints: bool, store: &'s LintStore) -> Self {
6368
let mut builder = LintLevelsBuilder {
6469
sess,
6570
sets: LintLevelSets::new(),
6671
cur: 0,
6772
id_to_set: Default::default(),
6873
warn_about_weird_lints,
74+
store,
6975
};
7076
builder.process_command_line(sess, store);
7177
assert_eq!(builder.sets.list.len(), 1);
@@ -120,36 +126,75 @@ impl<'s> LintLevelsBuilder<'s> {
120126
if let (Level::Forbid, old_src) =
121127
self.sets.get_lint_level(id.lint, self.cur, Some(&specs), &self.sess)
122128
{
123-
let mut diag_builder = struct_span_err!(
124-
self.sess,
125-
src.span(),
126-
E0453,
127-
"{}({}) incompatible with previous forbid",
128-
level.as_str(),
129-
src.name(),
129+
// Backwards compatibility check:
130+
//
131+
// We used to not consider `forbid(lint_group)`
132+
// as preventing `allow(lint)` for some lint `lint` in
133+
// `lint_group`. For now, issue a future-compatibility
134+
// warning for this case.
135+
let id_name = id.lint.name_lower();
136+
let fcw_warning = match old_src {
137+
LintLevelSource::Default => false,
138+
LintLevelSource::Node(symbol, _, _) => self.store.is_lint_group(symbol),
139+
LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol),
140+
};
141+
debug!(
142+
"fcw_warning={:?}, specs.get(&id) = {:?}, old_src={:?}, id_name={:?}",
143+
fcw_warning, specs, old_src, id_name
130144
);
131-
diag_builder.span_label(src.span(), "overruled by previous forbid");
132-
match old_src {
133-
LintLevelSource::Default => {
134-
diag_builder.note(&format!(
135-
"`forbid` lint level is the default for {}",
136-
id.to_string()
137-
));
138-
}
139-
LintLevelSource::Node(_, forbid_source_span, reason) => {
140-
diag_builder.span_label(forbid_source_span, "`forbid` level set here");
141-
if let Some(rationale) = reason {
142-
diag_builder.note(&rationale.as_str());
145+
146+
let decorate_diag_builder = |mut diag_builder: DiagnosticBuilder<'_>| {
147+
diag_builder.span_label(src.span(), "overruled by previous forbid");
148+
match old_src {
149+
LintLevelSource::Default => {
150+
diag_builder.note(&format!(
151+
"`forbid` lint level is the default for {}",
152+
id.to_string()
153+
));
154+
}
155+
LintLevelSource::Node(_, forbid_source_span, reason) => {
156+
diag_builder.span_label(forbid_source_span, "`forbid` level set here");
157+
if let Some(rationale) = reason {
158+
diag_builder.note(&rationale.as_str());
159+
}
160+
}
161+
LintLevelSource::CommandLine(_, _) => {
162+
diag_builder.note("`forbid` lint level was set on command line");
143163
}
144164
}
145-
LintLevelSource::CommandLine(_, _) => {
146-
diag_builder.note("`forbid` lint level was set on command line");
147-
}
165+
diag_builder.emit();
166+
};
167+
if !fcw_warning {
168+
let diag_builder = struct_span_err!(
169+
self.sess,
170+
src.span(),
171+
E0453,
172+
"{}({}) incompatible with previous forbid",
173+
level.as_str(),
174+
src.name(),
175+
);
176+
decorate_diag_builder(diag_builder);
177+
} else {
178+
self.struct_lint(
179+
FORBIDDEN_LINT_GROUPS,
180+
Some(src.span().into()),
181+
|diag_builder| {
182+
let diag_builder = diag_builder.build(&format!(
183+
"{}({}) incompatible with previous forbid",
184+
level.as_str(),
185+
src.name(),
186+
));
187+
decorate_diag_builder(diag_builder);
188+
},
189+
);
148190
}
149-
diag_builder.emit();
150191

151-
// Retain the forbid lint level
152-
return;
192+
// Retain the forbid lint level, unless we are
193+
// issuing a FCW. In the FCW case, we want to
194+
// respect the new setting.
195+
if !fcw_warning {
196+
return;
197+
}
153198
}
154199
}
155200
specs.insert(id, (level, src));

compiler/rustc_lint_defs/src/builtin.rs

+39
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,48 @@
44
//! compiler code, rather than using their own custom pass. Those
55
//! lints are all available in `rustc_lint::builtin`.
66
7+
// ignore-tidy-filelength
8+
79
use crate::{declare_lint, declare_lint_pass};
810
use rustc_span::edition::Edition;
911
use rustc_span::symbol::sym;
1012

13+
declare_lint! {
14+
/// The `forbidden_lint_groups` lint detects violations of
15+
/// `forbid` applied to a lint group. Due to a bug in the compiler,
16+
/// these used to be overlooked entirely. They now generate a warning.
17+
///
18+
/// ### Example
19+
///
20+
/// ```rust
21+
/// #![forbid(warnings)]
22+
/// #![deny(bad_style)]
23+
///
24+
/// fn main() {}
25+
/// ```
26+
///
27+
/// {{produces}}
28+
///
29+
/// ### Recommended fix
30+
///
31+
/// If your crate is using `#![forbid(warnings)]`,
32+
/// we recommend that you change to `#![deny(warnings)]`.
33+
///
34+
/// ### Explanation
35+
///
36+
/// Due to a compiler bug, applying `forbid` to lint groups
37+
/// previously had no effect. The bug is now fixed but instead of
38+
/// enforcing `forbid` we issue this future-compatibility warning
39+
/// to avoid breaking existing crates.
40+
pub FORBIDDEN_LINT_GROUPS,
41+
Warn,
42+
"applying forbid to lint-groups",
43+
@future_incompatible = FutureIncompatibleInfo {
44+
reference: "issue #81670 <https://github.com/rust-lang/rust/issues/81670>",
45+
edition: None,
46+
};
47+
}
48+
1149
declare_lint! {
1250
/// The `ill_formed_attribute_input` lint detects ill-formed attribute
1351
/// inputs that were previously accepted and used in practice.
@@ -2837,6 +2875,7 @@ declare_lint_pass! {
28372875
/// Does nothing as a lint pass, but registers some `Lint`s
28382876
/// that are used by other parts of the compiler.
28392877
HardwiredLints => [
2878+
FORBIDDEN_LINT_GROUPS,
28402879
ILLEGAL_FLOATING_POINT_LITERAL_PATTERN,
28412880
ARITHMETIC_OVERFLOW,
28422881
UNCONDITIONAL_PANIC,

compiler/rustc_middle/src/lint.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use rustc_data_structures::fx::FxHashMap;
55
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
66
use rustc_errors::{DiagnosticBuilder, DiagnosticId};
77
use rustc_hir::HirId;
8-
use rustc_session::lint::{builtin, Level, Lint, LintId};
8+
use rustc_session::lint::{
9+
builtin::{self, FORBIDDEN_LINT_GROUPS},
10+
Level, Lint, LintId,
11+
};
912
use rustc_session::{DiagnosticMessageId, Session};
1013
use rustc_span::hygiene::MacroKind;
1114
use rustc_span::source_map::{DesugaringKind, ExpnKind, MultiSpan};
@@ -89,7 +92,12 @@ impl LintLevelSets {
8992
// If we're about to issue a warning, check at the last minute for any
9093
// directives against the warnings "lint". If, for example, there's an
9194
// `allow(warnings)` in scope then we want to respect that instead.
92-
if level == Level::Warn {
95+
//
96+
// We exempt `FORBIDDEN_LINT_GROUPS` from this because it specifically
97+
// triggers in cases (like #80988) where you have `forbid(warnings)`,
98+
// and so if we turned that into an error, it'd defeat the purpose of the
99+
// future compatibility warning.
100+
if level == Level::Warn && LintId::of(lint) != LintId::of(FORBIDDEN_LINT_GROUPS) {
93101
let (warnings_level, warnings_src) =
94102
self.get_lint_id_level(LintId::of(builtin::WARNINGS), idx, aux);
95103
if let Some(configured_warning_level) = warnings_level {
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Check what happens when we forbid a smaller group but
2+
// then allow a superset of that group.
3+
4+
#![forbid(nonstandard_style)]
5+
6+
// FIXME: Arguably this should be an error, but the WARNINGS group is
7+
// treated in a very special (and rather ad-hoc) way and
8+
// it fails to trigger.
9+
#[allow(warnings)]
10+
fn main() {
11+
let A: ();
12+
//~^ ERROR should have a snake case name
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: variable `A` should have a snake case name
2+
--> $DIR/forbid-group-group-1.rs:11:9
3+
|
4+
LL | let A: ();
5+
| ^ help: convert the identifier to snake case: `a`
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/forbid-group-group-1.rs:4:11
9+
|
10+
LL | #![forbid(nonstandard_style)]
11+
| ^^^^^^^^^^^^^^^^^
12+
= note: `#[forbid(non_snake_case)]` implied by `#[forbid(nonstandard_style)]`
13+
14+
error: aborting due to previous error
15+
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Check what happens when we forbid a bigger group but
2+
// then deny a subset of that group.
3+
4+
#![forbid(warnings)]
5+
#![deny(forbidden_lint_groups)]
6+
7+
#[allow(nonstandard_style)]
8+
//~^ ERROR incompatible with previous
9+
//~| WARNING previously accepted by the compiler
10+
//~| ERROR incompatible with previous
11+
//~| WARNING previously accepted by the compiler
12+
//~| ERROR incompatible with previous
13+
//~| WARNING previously accepted by the compiler
14+
//~| ERROR incompatible with previous
15+
//~| WARNING previously accepted by the compiler
16+
//~| ERROR incompatible with previous
17+
//~| WARNING previously accepted by the compiler
18+
//~| ERROR incompatible with previous
19+
//~| WARNING previously accepted by the compiler
20+
//~| ERROR incompatible with previous
21+
//~| WARNING previously accepted by the compiler
22+
//~| ERROR incompatible with previous
23+
//~| WARNING previously accepted by the compiler
24+
//~| ERROR incompatible with previous
25+
//~| WARNING previously accepted by the compiler
26+
fn main() {}

0 commit comments

Comments
 (0)