Skip to content

Commit 167ac05

Browse files
committed
Warn about const instability wrt MSRV
This makes `const` contexts use the `const`-stability version information instead of the regular stability one, as `const`-stability may happen much later than stability in non-`const` contexts.
1 parent e62e27b commit 167ac05

File tree

3 files changed

+107
-29
lines changed

3 files changed

+107
-29
lines changed

clippy_lints/src/incompatible_msrv.rs

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use clippy_config::Conf;
22
use clippy_utils::diagnostics::span_lint_and_then;
3-
use clippy_utils::is_in_test;
43
use clippy_utils::msrvs::Msrv;
5-
use rustc_attr_data_structures::{RustcVersion, Stability, StableSince};
4+
use clippy_utils::{is_in_const_context, is_in_test};
5+
use rustc_attr_data_structures::{RustcVersion, StabilityLevel, StableSince};
66
use rustc_data_structures::fx::FxHashMap;
7+
use rustc_hir::def::DefKind;
78
use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, QPath};
89
use rustc_lint::{LateContext, LateLintPass};
910
use rustc_middle::ty::TyCtxt;
@@ -80,7 +81,7 @@ enum Availability {
8081

8182
pub struct IncompatibleMsrv {
8283
msrv: Msrv,
83-
availability_cache: FxHashMap<DefId, Availability>,
84+
availability_cache: FxHashMap<(DefId, bool), Availability>,
8485
check_in_tests: bool,
8586
}
8687

@@ -96,29 +97,44 @@ impl IncompatibleMsrv {
9697
}
9798

9899
/// Returns the availability of `def_id`, whether it is enabled through a feature or
99-
/// available since a given version (the default being Rust 1.0.0).
100-
fn get_def_id_availability(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> Availability {
101-
if let Some(availability) = self.availability_cache.get(&def_id) {
100+
/// available since a given version (the default being Rust 1.0.0). `needs_const` requires
101+
/// the `const`-stability to be looked up instead of the stability in non-`const` contexts.
102+
fn get_def_id_availability(&mut self, tcx: TyCtxt<'_>, def_id: DefId, needs_const: bool) -> Availability {
103+
if let Some(availability) = self.availability_cache.get(&(def_id, needs_const)) {
102104
return *availability;
103105
}
104-
let stability = tcx.lookup_stability(def_id);
105-
let version = if stability.is_some_and(|stability| tcx.features().enabled(stability.feature)) {
106+
let (feature, stability_level) = if needs_const {
107+
tcx.lookup_const_stability(def_id)
108+
.map(|stability| (stability.feature, stability.level))
109+
.unzip()
110+
} else {
111+
tcx.lookup_stability(def_id)
112+
.map(|stability| (stability.feature, stability.level))
113+
.unzip()
114+
};
115+
let version = if feature.is_some_and(|feature| tcx.features().enabled(feature)) {
106116
Availability::FeatureEnabled
107-
} else if let Some(StableSince::Version(version)) = stability.as_ref().and_then(Stability::stable_since) {
117+
} else if let Some(StableSince::Version(version)) =
118+
stability_level.as_ref().and_then(StabilityLevel::stable_since)
119+
{
108120
Availability::Since(version)
121+
} else if needs_const {
122+
// Fallback to regular stability
123+
self.get_def_id_availability(tcx, def_id, false)
109124
} else if let Some(parent_def_id) = tcx.opt_parent(def_id) {
110-
self.get_def_id_availability(tcx, parent_def_id)
125+
self.get_def_id_availability(tcx, parent_def_id, needs_const)
111126
} else {
112127
Availability::Since(RustcVersion {
113128
major: 1,
114129
minor: 0,
115130
patch: 0,
116131
})
117132
};
118-
self.availability_cache.insert(def_id, version);
133+
self.availability_cache.insert((def_id, needs_const), version);
119134
version
120135
}
121136

137+
/// Emit lint if `def_id`, associated with `node` and `span`, is below the current MSRV.
122138
fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, node: HirId, span: Span) {
123139
if def_id.is_local() {
124140
// We don't check local items since their MSRV is supposed to always be valid.
@@ -144,17 +160,22 @@ impl IncompatibleMsrv {
144160
return;
145161
}
146162

163+
let needs_const = cx.enclosing_body.is_some()
164+
&& is_in_const_context(cx)
165+
&& matches!(cx.tcx.def_kind(def_id), DefKind::AssocFn | DefKind::Fn);
166+
147167
if (self.check_in_tests || !is_in_test(cx.tcx, node))
148168
&& let Some(current) = self.msrv.current(cx)
149-
&& let Availability::Since(version) = self.get_def_id_availability(cx.tcx, def_id)
169+
&& let Availability::Since(version) = self.get_def_id_availability(cx.tcx, def_id, needs_const)
150170
&& version > current
151171
{
152172
span_lint_and_then(
153173
cx,
154174
INCOMPATIBLE_MSRV,
155175
span,
156176
format!(
157-
"current MSRV (Minimum Supported Rust Version) is `{current}` but this item is stable since `{version}`"
177+
"current MSRV (Minimum Supported Rust Version) is `{current}` but this item is stable{} since `{version}`",
178+
if needs_const { " in a `const` context" } else { "" },
158179
),
159180
|diag| {
160181
if is_under_cfg_attribute(cx, node) {
@@ -168,7 +189,6 @@ impl IncompatibleMsrv {
168189

169190
impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
170191
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
171-
// TODO: check for const stability when in const context
172192
match expr.kind {
173193
ExprKind::MethodCall(_, _, _, span) => {
174194
if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {

tests/ui/incompatible_msrv.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#![feature(strict_provenance)] // For use in test
55
#![clippy::msrv = "1.3.0"]
66

7+
use std::cell::Cell;
78
use std::collections::HashMap;
89
use std::collections::hash_map::Entry;
910
use std::future::Future;
@@ -128,4 +129,43 @@ fn non_fn_items() {
128129
//~^ incompatible_msrv
129130
}
130131

132+
#[clippy::msrv = "1.87.0"]
133+
fn msrv_non_ok_in_const() {
134+
{
135+
let c = Cell::new(42);
136+
_ = c.get();
137+
}
138+
const {
139+
let c = Cell::new(42);
140+
_ = c.get();
141+
//~^ incompatible_msrv
142+
}
143+
}
144+
145+
#[clippy::msrv = "1.88.0"]
146+
fn msrv_ok_in_const() {
147+
{
148+
let c = Cell::new(42);
149+
_ = c.get();
150+
}
151+
const {
152+
let c = Cell::new(42);
153+
_ = c.get();
154+
}
155+
}
156+
157+
#[clippy::msrv = "1.86.0"]
158+
fn enum_variant_not_ok() {
159+
let _ = std::io::ErrorKind::InvalidFilename;
160+
//~^ incompatible_msrv
161+
let _ = const { std::io::ErrorKind::InvalidFilename };
162+
//~^ incompatible_msrv
163+
}
164+
165+
#[clippy::msrv = "1.87.0"]
166+
fn enum_variant_ok() {
167+
let _ = std::io::ErrorKind::InvalidFilename;
168+
let _ = const { std::io::ErrorKind::InvalidFilename };
169+
}
170+
131171
fn main() {}

tests/ui/incompatible_msrv.stderr

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.10.0`
2-
--> tests/ui/incompatible_msrv.rs:15:39
2+
--> tests/ui/incompatible_msrv.rs:16:39
33
|
44
LL | assert_eq!(map.entry("poneyland").key(), &"poneyland");
55
| ^^^^^
@@ -8,37 +8,37 @@ LL | assert_eq!(map.entry("poneyland").key(), &"poneyland");
88
= help: to override `-D warnings` add `#[allow(clippy::incompatible_msrv)]`
99

1010
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.12.0`
11-
--> tests/ui/incompatible_msrv.rs:21:11
11+
--> tests/ui/incompatible_msrv.rs:22:11
1212
|
1313
LL | v.into_key();
1414
| ^^^^^^^^^^
1515

1616
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.4.0`
17-
--> tests/ui/incompatible_msrv.rs:25:5
17+
--> tests/ui/incompatible_msrv.rs:26:5
1818
|
1919
LL | sleep(Duration::new(1, 0));
2020
| ^^^^^
2121

2222
error: current MSRV (Minimum Supported Rust Version) is `1.2.0` but this item is stable since `1.3.0`
23-
--> tests/ui/incompatible_msrv.rs:30:33
23+
--> tests/ui/incompatible_msrv.rs:31:33
2424
|
2525
LL | static NO_BODY_BAD_MSRV: Option<Duration> = None;
2626
| ^^^^^^^^
2727

2828
error: current MSRV (Minimum Supported Rust Version) is `1.2.0` but this item is stable since `1.3.0`
29-
--> tests/ui/incompatible_msrv.rs:37:19
29+
--> tests/ui/incompatible_msrv.rs:38:19
3030
|
3131
LL | let _: Option<Duration> = None;
3232
| ^^^^^^^^
3333

3434
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.43.0`
35-
--> tests/ui/incompatible_msrv.rs:61:17
35+
--> tests/ui/incompatible_msrv.rs:62:17
3636
|
3737
LL | let _ = core::iter::once_with(|| 0);
3838
| ^^^^^^^^^^^^^^^^^^^^^
3939

4040
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.43.0`
41-
--> tests/ui/incompatible_msrv.rs:68:21
41+
--> tests/ui/incompatible_msrv.rs:69:21
4242
|
4343
LL | let _ = core::iter::once_with(|| $msg);
4444
| ^^^^^^^^^^^^^^^^^^^^^
@@ -49,48 +49,66 @@ LL | my_panic!("foo");
4949
= note: this error originates in the macro `my_panic` (in Nightly builds, run with -Z macro-backtrace for more info)
5050

5151
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.43.0`
52-
--> tests/ui/incompatible_msrv.rs:75:13
52+
--> tests/ui/incompatible_msrv.rs:76:13
5353
|
5454
LL | assert!(core::iter::once_with(|| 0).next().is_some());
5555
| ^^^^^^^^^^^^^^^^^^^^^
5656

5757
error: current MSRV (Minimum Supported Rust Version) is `1.80.0` but this item is stable since `1.82.0`
58-
--> tests/ui/incompatible_msrv.rs:88:13
58+
--> tests/ui/incompatible_msrv.rs:89:13
5959
|
6060
LL | let _ = std::iter::repeat_n((), 5);
6161
| ^^^^^^^^^^^^^^^^^^^
6262

6363
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0`
64-
--> tests/ui/incompatible_msrv.rs:99:13
64+
--> tests/ui/incompatible_msrv.rs:100:13
6565
|
6666
LL | let _ = std::iter::repeat_n((), 5);
6767
| ^^^^^^^^^^^^^^^^^^^
6868

6969
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0`
70-
--> tests/ui/incompatible_msrv.rs:104:17
70+
--> tests/ui/incompatible_msrv.rs:105:17
7171
|
7272
LL | let _ = std::iter::repeat_n((), 5);
7373
| ^^^^^^^^^^^^^^^^^^^
7474
|
7575
= note: you may want to conditionally increase the MSRV considered by Clippy using the `clippy::msrv` attribute
7676

7777
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0`
78-
--> tests/ui/incompatible_msrv.rs:109:17
78+
--> tests/ui/incompatible_msrv.rs:110:17
7979
|
8080
LL | let _ = std::iter::repeat_n((), 5);
8181
| ^^^^^^^^^^^^^^^^^^^
8282

8383
error: current MSRV (Minimum Supported Rust Version) is `1.78.0` but this item is stable since `1.84.0`
84-
--> tests/ui/incompatible_msrv.rs:122:7
84+
--> tests/ui/incompatible_msrv.rs:123:7
8585
|
8686
LL | r.isqrt()
8787
| ^^^^^^^
8888

8989
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.85.0`
90-
--> tests/ui/incompatible_msrv.rs:127:13
90+
--> tests/ui/incompatible_msrv.rs:128:13
9191
|
9292
LL | let _ = std::io::ErrorKind::CrossesDevices;
9393
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9494

95-
error: aborting due to 14 previous errors
95+
error: current MSRV (Minimum Supported Rust Version) is `1.87.0` but this item is stable in a `const` context since `1.88.0`
96+
--> tests/ui/incompatible_msrv.rs:140:15
97+
|
98+
LL | _ = c.get();
99+
| ^^^^^
100+
101+
error: current MSRV (Minimum Supported Rust Version) is `1.86.0` but this item is stable since `1.87.0`
102+
--> tests/ui/incompatible_msrv.rs:159:13
103+
|
104+
LL | let _ = std::io::ErrorKind::InvalidFilename;
105+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
106+
107+
error: current MSRV (Minimum Supported Rust Version) is `1.86.0` but this item is stable since `1.87.0`
108+
--> tests/ui/incompatible_msrv.rs:161:21
109+
|
110+
LL | let _ = const { std::io::ErrorKind::InvalidFilename };
111+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
112+
113+
error: aborting due to 17 previous errors
96114

0 commit comments

Comments
 (0)