Skip to content

[missing_const_for_fn]: Ensure dropped locals are ~const Destruct #10891

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clippy_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ extern crate rustc_ast;
extern crate rustc_ast_pretty;
extern crate rustc_attr;
extern crate rustc_data_structures;
extern crate rustc_const_eval;
// The `rustc_driver` crate seems to be required in order to use the `rust_ast` crate.
#[allow(unused_extern_crates)]
extern crate rustc_driver;
Expand Down
57 changes: 48 additions & 9 deletions clippy_utils/src/qualify_min_const_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@
// differ from the time of `rustc` even if the name stays the same.

use crate::msrvs::Msrv;
use hir::{Constness, LangItem};
use rustc_const_eval::transform::check_consts::ConstCx;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::Obligation;
use rustc_middle::mir::{
Body, CastKind, NonDivergingIntrinsic, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind,
Terminator, TerminatorKind,
};
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt};
use rustc_middle::ty::{BoundConstness, TraitRef};
use rustc_semver::RustcVersion;
use rustc_span::symbol::sym;
use rustc_span::Span;
use rustc_trait_selection::traits::SelectionContext;
use std::borrow::Cow;

type McfResult = Result<(), (Span, Cow<'static, str>)>;
Expand Down Expand Up @@ -266,6 +273,7 @@ fn check_operand<'tcx>(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, b

fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
let mut cursor = place.projection.as_ref();

while let [ref proj_base @ .., elem] = *cursor {
cursor = proj_base;
match elem {
Expand Down Expand Up @@ -305,15 +313,19 @@ fn check_terminator<'tcx>(
| TerminatorKind::Resume
| TerminatorKind::Terminate
| TerminatorKind::Unreachable => Ok(()),

TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, body),

TerminatorKind::Drop { place, .. } => {
if !is_ty_const_destruct(tcx, place.ty(&body.local_decls, tcx).ty, body) {
return Err((
span,
"cannot drop locals with a non constant destructor in const fn".into(),
));
}
check_place(tcx, *place, span, body)
},
TerminatorKind::SwitchInt { discr, targets: _ } => check_operand(tcx, discr, span, body),

TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => {
Err((span, "const fn generators are unstable".into()))
},

TerminatorKind::Call {
func,
args,
Expand Down Expand Up @@ -357,15 +369,13 @@ fn check_terminator<'tcx>(
Err((span, "can only call other const fns within const fn".into()))
}
},

TerminatorKind::Assert {
cond,
expected: _,
msg: _,
target: _,
unwind: _,
} => check_operand(tcx, cond, span, body),

TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
}
}
Expand All @@ -379,8 +389,7 @@ fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool {
// as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262.

// HACK(nilstrieb): CURRENT_RUSTC_VERSION can return versions like 1.66.0-dev. `rustc-semver`
// doesn't accept the `-dev` version number so we have to strip it
// off.
// doesn't accept the `-dev` version number so we have to strip it off.
let short_version = since
.as_str()
.split('-')
Expand All @@ -398,3 +407,33 @@ fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool {
}
})
}

fn is_ty_const_destruct<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool {
// Avoid selecting for simple cases, such as builtin types.
if ty::util::is_trivially_const_drop(ty) {
return true;
}

let obligation = Obligation::new(
tcx,
ObligationCause::dummy_with_span(body.span),
ConstCx::new(tcx, body).param_env.with_constness(Constness::Const),
TraitRef::from_lang_item(tcx, LangItem::Destruct, body.span, [ty]).with_constness(BoundConstness::ConstIfConst),
);

let fields_all_const_destruct = if let ty::Adt(def, subst) = ty.kind() && !ty.is_union() {
// This is such a mess even rustfmt doesn't wanna touch it
def.all_fields()
.map(|field| is_ty_const_destruct(tcx, field.ty(tcx, subst), body))
.all(|f| f)
&& def.variants().iter()
.map(|variant| variant.fields.iter().map(|field| is_ty_const_destruct(tcx, field.ty(tcx, subst), body)))
.all(|mut fs| fs.all(|f| f))
} else {
true
};

let infcx = tcx.infer_ctxt().build();
let mut selcx = SelectionContext::new(&infcx);
selcx.select(&obligation).is_ok() && fields_all_const_destruct
}
13 changes: 12 additions & 1 deletion tests/ui/missing_const_for_fn/cant_be_const.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ extern crate proc_macros;

use proc_macros::with_span;

struct Game;
struct Game; // You just lost.

// This should not be linted because it's already const
const fn already_const() -> i32 {
Expand Down Expand Up @@ -126,3 +126,14 @@ with_span! {
span
fn dont_check_in_proc_macro() {}
}

fn a(this: String) {}

enum A {
F(String),
N,
}

fn b(this: A) {}

fn c(this: Vec<u16>) {}