missing_const_for_fn: consider constness of instance

When determining when a function or method can be called from a `const`
context, the determination must be made on the instance, not on the
declaration. This makes a difference, for example, with `const_trait`
traits whose implementations may or may not be `const`.
This commit is contained in:
Samuel Tardieu 2025-05-08 18:23:49 +02:00
parent e04158caed
commit 43b7d87be1
No known key found for this signature in database
GPG key ID: BDDC3208C6FEAFA8
4 changed files with 99 additions and 2 deletions

View file

@ -18,7 +18,7 @@ use rustc_middle::mir::{
};
use rustc_middle::traits::{BuiltinImplSource, ImplSource, ObligationCause};
use rustc_middle::ty::adjustment::PointerCoercion;
use rustc_middle::ty::{self, GenericArgKind, TraitRef, Ty, TyCtxt};
use rustc_middle::ty::{self, GenericArgKind, Instance, TraitRef, Ty, TyCtxt};
use rustc_span::Span;
use rustc_span::symbol::sym;
use rustc_trait_selection::traits::{ObligationCtxt, SelectionContext};
@ -349,7 +349,15 @@ fn check_terminator<'tcx>(
}
| TerminatorKind::TailCall { func, args, fn_span: _ } => {
let fn_ty = func.ty(body, cx.tcx);
if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
if let ty::FnDef(fn_def_id, fn_substs) = fn_ty.kind() {
// FIXME: when analyzing a function with generic parameters, we may not have enough information to
// resolve to an instance. However, we could check if a host effect predicate can guarantee that
// this can be made a `const` call.
let fn_def_id = match Instance::try_resolve(cx.tcx, cx.typing_env(), *fn_def_id, fn_substs) {
Ok(Some(fn_inst)) => fn_inst.def_id(),
Ok(None) => return Err((span, format!("cannot resolve instance for {func:?}").into())),
Err(_) => return Err((span, format!("error during instance resolution of {func:?}").into())),
};
if !is_stable_const_fn(cx, fn_def_id, msrv) {
return Err((
span,

View file

@ -0,0 +1,36 @@
#![feature(const_trait_impl)]
#![warn(clippy::missing_const_for_fn)]
// Reduced test case from https://github.com/rust-lang/rust-clippy/issues/14658
#[const_trait]
trait ConstTrait {
fn method(self);
}
impl ConstTrait for u32 {
fn method(self) {}
}
impl const ConstTrait for u64 {
fn method(self) {}
}
fn cannot_be_const() {
0u32.method();
}
//~v missing_const_for_fn
const fn can_be_const() {
0u64.method();
}
// False negative, see FIXME comment in `clipy_utils::qualify_min_const`
fn could_be_const_but_does_not_trigger<T>(t: T)
where
T: const ConstTrait,
{
t.method();
}
fn main() {}

View file

@ -0,0 +1,36 @@
#![feature(const_trait_impl)]
#![warn(clippy::missing_const_for_fn)]
// Reduced test case from https://github.com/rust-lang/rust-clippy/issues/14658
#[const_trait]
trait ConstTrait {
fn method(self);
}
impl ConstTrait for u32 {
fn method(self) {}
}
impl const ConstTrait for u64 {
fn method(self) {}
}
fn cannot_be_const() {
0u32.method();
}
//~v missing_const_for_fn
fn can_be_const() {
0u64.method();
}
// False negative, see FIXME comment in `clipy_utils::qualify_min_const`
fn could_be_const_but_does_not_trigger<T>(t: T)
where
T: const ConstTrait,
{
t.method();
}
fn main() {}

View file

@ -0,0 +1,17 @@
error: this could be a `const fn`
--> tests/ui/missing_const_for_fn/const_trait.rs:24:1
|
LL | / fn can_be_const() {
LL | | 0u64.method();
LL | | }
| |_^
|
= note: `-D clippy::missing-const-for-fn` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::missing_const_for_fn)]`
help: make the function `const`
|
LL | const fn can_be_const() {
| +++++
error: aborting due to 1 previous error