Skip to content

Commit bcee65a

Browse files
committed
Implement internal lint for MSRV lints
This internal lint checks if the `extract_msrv_attrs!` macro is used if a lint has a MSRV. If not, it suggests to add this attribute to the lint pass implementation.
1 parent 4417f78 commit bcee65a

10 files changed

+175
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ syn = { version = "1.0", features = ["full"] }
5050
futures = "0.3"
5151
parking_lot = "0.11.2"
5252
tokio = { version = "1", features = ["io-util"] }
53+
rustc-semver = "1.1"
5354
num_cpus = "1.13"
5455

5556
[build-dependencies]

clippy_lints/src/lib.register_internal.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
1010
LintId::of(utils::internal_lints::IF_CHAIN_STYLE),
1111
LintId::of(utils::internal_lints::INTERNING_DEFINED_SYMBOL),
1212
LintId::of(utils::internal_lints::INVALID_CLIPPY_VERSION_ATTRIBUTE),
13+
LintId::of(utils::internal_lints::INVALID_MSRV_ATTR_IMPL),
1314
LintId::of(utils::internal_lints::INVALID_PATHS),
1415
LintId::of(utils::internal_lints::LINT_WITHOUT_LINT_PASS),
1516
LintId::of(utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM),

clippy_lints/src/lib.register_lints.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ store.register_lints(&[
1818
#[cfg(feature = "internal")]
1919
utils::internal_lints::INVALID_CLIPPY_VERSION_ATTRIBUTE,
2020
#[cfg(feature = "internal")]
21+
utils::internal_lints::INVALID_MSRV_ATTR_IMPL,
22+
#[cfg(feature = "internal")]
2123
utils::internal_lints::INVALID_PATHS,
2224
#[cfg(feature = "internal")]
2325
utils::internal_lints::LINT_WITHOUT_LINT_PASS,

clippy_lints/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
505505
store.register_late_pass(|| Box::new(utils::internal_lints::LintWithoutLintPass::default()));
506506
store.register_late_pass(|| Box::new(utils::internal_lints::MatchTypeOnDiagItem));
507507
store.register_late_pass(|| Box::new(utils::internal_lints::OuterExpnDataPass));
508+
store.register_late_pass(|| Box::new(utils::internal_lints::MsrvAttrImpl));
508509
}
509510

510511
store.register_late_pass(|| Box::new(utils::author::Author));

clippy_lints/src/utils/internal_lints.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use rustc_hir::{
2525
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
2626
use rustc_middle::hir::nested_filter;
2727
use rustc_middle::mir::interpret::ConstValue;
28-
use rustc_middle::ty;
28+
use rustc_middle::ty::{self, subst::GenericArgKind};
2929
use rustc_semver::RustcVersion;
3030
use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
3131
use rustc_span::source_map::Spanned;
@@ -337,6 +337,15 @@ declare_clippy_lint! {
337337
"found clippy lint without `clippy::version` attribute"
338338
}
339339

340+
declare_clippy_lint! {
341+
/// ### What it does
342+
/// Check that the `extract_msrv_attr!` macro is used, when a lint has a MSRV.
343+
///
344+
pub INVALID_MSRV_ATTR_IMPL,
345+
internal,
346+
"checking if all necessary steps were taken when adding a MSRV to a lint"
347+
}
348+
340349
declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
341350

342351
impl EarlyLintPass for ClippyLintsInternal {
@@ -1314,3 +1323,46 @@ fn if_chain_local_span(cx: &LateContext<'_>, local: &Local<'_>, if_chain_span: S
13141323
span.parent(),
13151324
)
13161325
}
1326+
1327+
declare_lint_pass!(MsrvAttrImpl => [INVALID_MSRV_ATTR_IMPL]);
1328+
1329+
impl LateLintPass<'_> for MsrvAttrImpl {
1330+
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
1331+
if_chain! {
1332+
if let hir::ItemKind::Impl(hir::Impl {
1333+
of_trait: Some(lint_pass_trait_ref),
1334+
self_ty,
1335+
items,
1336+
..
1337+
}) = &item.kind;
1338+
if let Some(lint_pass_trait_def_id) = lint_pass_trait_ref.trait_def_id();
1339+
let is_late_pass = match_def_path(cx, lint_pass_trait_def_id, &paths::LATE_LINT_PASS);
1340+
if is_late_pass || match_def_path(cx, lint_pass_trait_def_id, &paths::EARLY_LINT_PASS);
1341+
let self_ty = hir_ty_to_ty(cx.tcx, self_ty);
1342+
if let ty::Adt(self_ty_def, _) = self_ty.kind();
1343+
if self_ty_def.is_struct();
1344+
if self_ty_def.all_fields().any(|f| {
1345+
cx.tcx
1346+
.type_of(f.did)
1347+
.walk()
1348+
.filter(|t| matches!(t.unpack(), GenericArgKind::Type(_)))
1349+
.any(|t| match_type(cx, t.expect_ty(), &["rustc_semver", "RustcVersion"]))
1350+
});
1351+
if !items.iter().any(|item| item.ident.name.as_str() == "enter_lint_attrs");
1352+
then {
1353+
let context = if is_late_pass { "LateContext" } else { "EarlyContext" };
1354+
let lint_pass = if is_late_pass { "LateLintPass" } else { "EarlyLintPass" };
1355+
let span = cx.sess().source_map().span_through_char(item.span, '{');
1356+
span_lint_and_sugg(
1357+
cx,
1358+
INVALID_MSRV_ATTR_IMPL,
1359+
span,
1360+
&format!("`extract_msrv_attr!` macro missing from `{lint_pass}` implementation"),
1361+
&format!("add `extract_msrv_attr!({context})` to the `{lint_pass}` implementation"),
1362+
format!("{}\n extract_msrv_attr!({context});", snippet(cx, span, "..")),
1363+
Applicability::MachineApplicable,
1364+
);
1365+
}
1366+
}
1367+
}
1368+
}

clippy_utils/src/paths.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub const DIR_BUILDER: [&str; 3] = ["std", "fs", "DirBuilder"];
3232
pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
3333
#[cfg(feature = "internal")]
3434
pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
35+
#[cfg(feature = "internal")]
36+
pub const EARLY_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "EarlyLintPass"];
3537
pub const EXIT: [&str; 3] = ["std", "process", "exit"];
3638
pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
3739
pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
@@ -67,6 +69,8 @@ pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
6769
#[cfg(feature = "internal")]
6870
pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
6971
#[cfg(feature = "internal")]
72+
pub const LATE_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "LateLintPass"];
73+
#[cfg(feature = "internal")]
7074
pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
7175
pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
7276
pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];

tests/compile-test.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ static TEST_DEPENDENCIES: &[&str] = &[
3434
"syn",
3535
"tokio",
3636
"parking_lot",
37+
"rustc_semver",
3738
];
3839

3940
// Test dependencies may need an `extern crate` here to ensure that they show up
@@ -53,6 +54,8 @@ extern crate parking_lot;
5354
#[allow(unused_extern_crates)]
5455
extern crate quote;
5556
#[allow(unused_extern_crates)]
57+
extern crate rustc_semver;
58+
#[allow(unused_extern_crates)]
5659
extern crate syn;
5760
#[allow(unused_extern_crates)]
5861
extern crate tokio;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// run-rustfix
2+
3+
#![deny(clippy::internal)]
4+
#![allow(clippy::missing_clippy_version_attribute)]
5+
#![feature(rustc_private)]
6+
7+
extern crate rustc_ast;
8+
extern crate rustc_hir;
9+
extern crate rustc_lint;
10+
extern crate rustc_middle;
11+
#[macro_use]
12+
extern crate rustc_session;
13+
use clippy_utils::extract_msrv_attr;
14+
use rustc_hir::Expr;
15+
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
16+
use rustc_semver::RustcVersion;
17+
18+
declare_lint! {
19+
pub TEST_LINT,
20+
Warn,
21+
""
22+
}
23+
24+
struct Pass {
25+
msrv: Option<RustcVersion>,
26+
}
27+
28+
impl_lint_pass!(Pass => [TEST_LINT]);
29+
30+
impl LateLintPass<'_> for Pass {
31+
extract_msrv_attr!(LateContext);
32+
fn check_expr(&mut self, _: &LateContext<'_>, _: &Expr<'_>) {}
33+
}
34+
35+
impl EarlyLintPass for Pass {
36+
extract_msrv_attr!(EarlyContext);
37+
fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {}
38+
}
39+
40+
fn main() {}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// run-rustfix
2+
3+
#![deny(clippy::internal)]
4+
#![allow(clippy::missing_clippy_version_attribute)]
5+
#![feature(rustc_private)]
6+
7+
extern crate rustc_ast;
8+
extern crate rustc_hir;
9+
extern crate rustc_lint;
10+
extern crate rustc_middle;
11+
#[macro_use]
12+
extern crate rustc_session;
13+
use clippy_utils::extract_msrv_attr;
14+
use rustc_hir::Expr;
15+
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
16+
use rustc_semver::RustcVersion;
17+
18+
declare_lint! {
19+
pub TEST_LINT,
20+
Warn,
21+
""
22+
}
23+
24+
struct Pass {
25+
msrv: Option<RustcVersion>,
26+
}
27+
28+
impl_lint_pass!(Pass => [TEST_LINT]);
29+
30+
impl LateLintPass<'_> for Pass {
31+
fn check_expr(&mut self, _: &LateContext<'_>, _: &Expr<'_>) {}
32+
}
33+
34+
impl EarlyLintPass for Pass {
35+
fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {}
36+
}
37+
38+
fn main() {}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
error: `extract_msrv_attr!` macro missing from `LateLintPass` implementation
2+
--> $DIR/invalid_msrv_attr_impl.rs:30:1
3+
|
4+
LL | impl LateLintPass<'_> for Pass {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/invalid_msrv_attr_impl.rs:3:9
9+
|
10+
LL | #![deny(clippy::internal)]
11+
| ^^^^^^^^^^^^^^^^
12+
= note: `#[deny(clippy::invalid_msrv_attr_impl)]` implied by `#[deny(clippy::internal)]`
13+
help: add `extract_msrv_attr!(LateContext)` to the `LateLintPass` implementation
14+
|
15+
LL + impl LateLintPass<'_> for Pass {
16+
LL + extract_msrv_attr!(LateContext);
17+
|
18+
19+
error: `extract_msrv_attr!` macro missing from `EarlyLintPass` implementation
20+
--> $DIR/invalid_msrv_attr_impl.rs:34:1
21+
|
22+
LL | impl EarlyLintPass for Pass {
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
24+
|
25+
help: add `extract_msrv_attr!(EarlyContext)` to the `EarlyLintPass` implementation
26+
|
27+
LL + impl EarlyLintPass for Pass {
28+
LL + extract_msrv_attr!(EarlyContext);
29+
|
30+
31+
error: aborting due to 2 previous errors
32+

0 commit comments

Comments
 (0)