Skip to content

Commit 04ed08b

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 ed176b7 commit 04ed08b

File tree

3 files changed

+105
-27
lines changed

3 files changed

+105
-27
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::{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;
@@ -110,4 +111,43 @@ fn non_fn_items() {
110111
//~^ incompatible_msrv
111112
}
112113

114+
#[clippy::msrv = "1.87.0"]
115+
fn msrv_non_ok_in_const() {
116+
{
117+
let c = Cell::new(42);
118+
_ = c.get();
119+
}
120+
const {
121+
let c = Cell::new(42);
122+
_ = c.get();
123+
//~^ incompatible_msrv
124+
}
125+
}
126+
127+
#[clippy::msrv = "1.88.0"]
128+
fn msrv_ok_in_const() {
129+
{
130+
let c = Cell::new(42);
131+
_ = c.get();
132+
}
133+
const {
134+
let c = Cell::new(42);
135+
_ = c.get();
136+
}
137+
}
138+
139+
#[clippy::msrv = "1.86.0"]
140+
fn enum_variant_not_ok() {
141+
let _ = std::io::ErrorKind::InvalidFilename;
142+
//~^ incompatible_msrv
143+
let _ = const { std::io::ErrorKind::InvalidFilename };
144+
//~^ incompatible_msrv
145+
}
146+
147+
#[clippy::msrv = "1.87.0"]
148+
fn enum_variant_ok() {
149+
let _ = std::io::ErrorKind::InvalidFilename;
150+
let _ = const { std::io::ErrorKind::InvalidFilename };
151+
}
152+
113153
fn main() {}

tests/ui/incompatible_msrv.stderr

Lines changed: 31 additions & 13 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,25 +8,25 @@ 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.3.0` but this item is stable since `1.43.0`
23-
--> tests/ui/incompatible_msrv.rs:49:17
23+
--> tests/ui/incompatible_msrv.rs:50:17
2424
|
2525
LL | let _ = core::iter::once_with(|| 0);
2626
| ^^^^^^^^^^^^^^^^^^^^^
2727

2828
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.43.0`
29-
--> tests/ui/incompatible_msrv.rs:56:21
29+
--> tests/ui/incompatible_msrv.rs:57:21
3030
|
3131
LL | let _ = core::iter::once_with(|| $msg);
3232
| ^^^^^^^^^^^^^^^^^^^^^
@@ -37,48 +37,66 @@ LL | my_panic!("foo");
3737
= note: this error originates in the macro `my_panic` (in Nightly builds, run with -Z macro-backtrace for more info)
3838

3939
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.43.0`
40-
--> tests/ui/incompatible_msrv.rs:63:13
40+
--> tests/ui/incompatible_msrv.rs:64:13
4141
|
4242
LL | assert!(core::iter::once_with(|| 0).next().is_some());
4343
| ^^^^^^^^^^^^^^^^^^^^^
4444

4545
error: current MSRV (Minimum Supported Rust Version) is `1.80.0` but this item is stable since `1.82.0`
46-
--> tests/ui/incompatible_msrv.rs:76:13
46+
--> tests/ui/incompatible_msrv.rs:77:13
4747
|
4848
LL | let _ = std::iter::repeat_n((), 5);
4949
| ^^^^^^^^^^^^^^^^^^^
5050

5151
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0`
52-
--> tests/ui/incompatible_msrv.rs:81:13
52+
--> tests/ui/incompatible_msrv.rs:82:13
5353
|
5454
LL | let _ = std::iter::repeat_n((), 5);
5555
| ^^^^^^^^^^^^^^^^^^^
5656

5757
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0`
58-
--> tests/ui/incompatible_msrv.rs:86:17
58+
--> tests/ui/incompatible_msrv.rs:87:17
5959
|
6060
LL | let _ = std::iter::repeat_n((), 5);
6161
| ^^^^^^^^^^^^^^^^^^^
6262
|
6363
= note: you may want to conditionally increase the MSRV considered by Clippy using the `clippy::msrv` attribute
6464

6565
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0`
66-
--> tests/ui/incompatible_msrv.rs:91:17
66+
--> tests/ui/incompatible_msrv.rs:92:17
6767
|
6868
LL | let _ = std::iter::repeat_n((), 5);
6969
| ^^^^^^^^^^^^^^^^^^^
7070

7171
error: current MSRV (Minimum Supported Rust Version) is `1.78.0` but this item is stable since `1.84.0`
72-
--> tests/ui/incompatible_msrv.rs:104:7
72+
--> tests/ui/incompatible_msrv.rs:105:7
7373
|
7474
LL | r.isqrt()
7575
| ^^^^^^^
7676

7777
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.85.0`
78-
--> tests/ui/incompatible_msrv.rs:109:13
78+
--> tests/ui/incompatible_msrv.rs:110:13
7979
|
8080
LL | let _ = std::io::ErrorKind::CrossesDevices;
8181
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8282

83-
error: aborting due to 12 previous errors
83+
error: current MSRV (Minimum Supported Rust Version) is `1.87.0` but this item is stable in a `const` context since `1.88.0`
84+
--> tests/ui/incompatible_msrv.rs:122:15
85+
|
86+
LL | _ = c.get();
87+
| ^^^^^
88+
89+
error: current MSRV (Minimum Supported Rust Version) is `1.86.0` but this item is stable since `1.87.0`
90+
--> tests/ui/incompatible_msrv.rs:141:13
91+
|
92+
LL | let _ = std::io::ErrorKind::InvalidFilename;
93+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
94+
95+
error: current MSRV (Minimum Supported Rust Version) is `1.86.0` but this item is stable since `1.87.0`
96+
--> tests/ui/incompatible_msrv.rs:143:21
97+
|
98+
LL | let _ = const { std::io::ErrorKind::InvalidFilename };
99+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
100+
101+
error: aborting due to 15 previous errors
84102

0 commit comments

Comments
 (0)