Skip to content

Commit 28b1fe5

Browse files
committed
Auto merge of #8313 - flip1995:msrv-internal-lint, r=xFrednet
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. Following up #8280 (comment). This currently doesn't implement the documentation check. But since this is just an extension of this lint, I think this is a good MVP of this lint. r? `@camsteffen` cc `@xFrednet` changelog: none
2 parents e511476 + bca4ee7 commit 28b1fe5

10 files changed

+177
-1
lines changed

Cargo.toml

+1
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

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
1414
LintId::of(utils::internal_lints::LINT_WITHOUT_LINT_PASS),
1515
LintId::of(utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM),
1616
LintId::of(utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE),
17+
LintId::of(utils::internal_lints::MISSING_MSRV_ATTR_IMPL),
1718
LintId::of(utils::internal_lints::OUTER_EXPN_EXPN_DATA),
1819
LintId::of(utils::internal_lints::PRODUCE_ICE),
1920
LintId::of(utils::internal_lints::UNNECESSARY_SYMBOL_STR),

clippy_lints/src/lib.register_lints.rs

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ store.register_lints(&[
2626
#[cfg(feature = "internal")]
2727
utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE,
2828
#[cfg(feature = "internal")]
29+
utils::internal_lints::MISSING_MSRV_ATTR_IMPL,
30+
#[cfg(feature = "internal")]
2931
utils::internal_lints::OUTER_EXPN_EXPN_DATA,
3032
#[cfg(feature = "internal")]
3133
utils::internal_lints::PRODUCE_ICE,

clippy_lints/src/lib.rs

+1
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

+53-1
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 MISSING_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 => [MISSING_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(), &paths::RUSTC_VERSION))
1350+
});
1351+
if !items.iter().any(|item| item.ident.name == sym!(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+
MISSING_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

+6
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"];
@@ -126,6 +130,8 @@ pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet",
126130
pub const RESULT: [&str; 3] = ["core", "result", "Result"];
127131
pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
128132
pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
133+
#[cfg(feature = "internal")]
134+
pub const RUSTC_VERSION: [&str; 2] = ["rustc_semver", "RustcVersion"];
129135
pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
130136
pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
131137
pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];

tests/compile-test.rs

+3
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;
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() {}
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() {}
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::missing_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)