Auto merge of #12681 - y21:issue12677, r=Jarcho

Let `qualify_min_const_fn` deal with drop terminators

Fixes #12677

The `method_accepts_droppable` check that was there seemed overly conservative.
> Returns true if any of the method parameters is a type that implements `Drop`.
> The method can't be made const then, because `drop` can't be const-evaluated.

Accepting parameters that implement `Drop` should still be fine as long as the parameter isn't actually dropped, as is the case in the linked issue where the droppable is moved into the return place. This more accurate analysis ("is there a `drop` terminator") is already done by `qualify_min_const_fn` [here](f5e250180c/clippy_utils/src/qualify_min_const_fn.rs (L298)), so I don't think this additional check is really necessary?

Fixing the other, second case in the linked issue was only slightly more involved, since `Vec::new()` is a function call that has the ability to panic, so there must be a `drop()` terminator for cleanup, however we should be able to freely ignore that. [Const checking ignores cleanup blocks](https://github.com/rust-lang/rust/blob/master/compiler/rustc_const_eval/src/transform/check_consts/check.rs#L382-L388), so we should, too?

r? `@Jarcho`

----

changelog: [`missing_const_for_fn`]: continue linting on fns with parameters implementing `Drop` if they're not actually dropped
This commit is contained in:
bors 2024-06-11 22:38:52 +00:00
commit 38d12a9bc0
4 changed files with 65 additions and 23 deletions

View file

@ -1,7 +1,6 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::qualify_min_const_fn::is_min_const_fn;
use clippy_utils::ty::has_drop;
use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_macro, trait_ref_of_method};
use rustc_hir as hir;
use rustc_hir::def_id::CRATE_DEF_ID;
@ -121,10 +120,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
}
},
FnKind::Method(_, sig, ..) => {
if trait_ref_of_method(cx, def_id).is_some()
|| already_const(sig.header)
|| method_accepts_droppable(cx, def_id)
{
if trait_ref_of_method(cx, def_id).is_some() || already_const(sig.header) {
return;
}
},
@ -151,26 +147,13 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
let mir = cx.tcx.optimized_mir(def_id);
if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, &self.msrv) {
if cx.tcx.is_const_fn_raw(def_id.to_def_id()) {
cx.tcx.dcx().span_err(span, err);
}
} else {
if let Ok(()) = is_min_const_fn(cx.tcx, mir, &self.msrv) {
span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`");
}
}
extract_msrv_attr!(LateContext);
}
/// Returns true if any of the method parameters is a type that implements `Drop`. The method
/// can't be made const then, because `drop` can't be const-evaluated.
fn method_accepts_droppable(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
// If any of the params are droppable, return true
sig.inputs().iter().any(|&ty| has_drop(cx, ty))
}
// We don't have to lint on something that's already `const`
#[must_use]
fn already_const(header: hir::FnHeader) -> bool {

View file

@ -40,9 +40,13 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv)
)?;
for bb in &*body.basic_blocks {
check_terminator(tcx, body, bb.terminator(), msrv)?;
for stmt in &bb.statements {
check_statement(tcx, body, def_id, stmt, msrv)?;
// Cleanup blocks are ignored entirely by const eval, so we can too:
// https://github.com/rust-lang/rust/blob/1dea922ea6e74f99a0e97de5cdb8174e4dea0444/compiler/rustc_const_eval/src/transform/check_consts/check.rs#L382
if !bb.is_cleanup {
check_terminator(tcx, body, bb.terminator(), msrv)?;
for stmt in &bb.statements {
check_statement(tcx, body, def_id, stmt, msrv)?;
}
}
}
Ok(())

View file

@ -141,3 +141,33 @@ mod msrv {
let _ = unsafe { bar.val };
}
}
mod issue12677 {
pub struct Wrapper {
pub strings: Vec<String>,
}
impl Wrapper {
#[must_use]
pub fn new(strings: Vec<String>) -> Self {
Self { strings }
}
#[must_use]
pub fn empty() -> Self {
Self { strings: Vec::new() }
}
}
pub struct Other {
pub text: String,
pub vec: Vec<String>,
}
impl Other {
pub fn new(text: String) -> Self {
let vec = Vec::new();
Self { text, vec }
}
}
}

View file

@ -130,5 +130,30 @@ LL | | let _ = unsafe { bar.val };
LL | | }
| |_____^
error: aborting due to 14 previous errors
error: this could be a `const fn`
--> tests/ui/missing_const_for_fn/could_be_const.rs:152:9
|
LL | / pub fn new(strings: Vec<String>) -> Self {
LL | | Self { strings }
LL | | }
| |_________^
error: this could be a `const fn`
--> tests/ui/missing_const_for_fn/could_be_const.rs:157:9
|
LL | / pub fn empty() -> Self {
LL | | Self { strings: Vec::new() }
LL | | }
| |_________^
error: this could be a `const fn`
--> tests/ui/missing_const_for_fn/could_be_const.rs:168:9
|
LL | / pub fn new(text: String) -> Self {
LL | | let vec = Vec::new();
LL | | Self { text, vec }
LL | | }
| |_________^
error: aborting due to 17 previous errors