about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_utils/src/lib.rs1
-rw-r--r--clippy_utils/src/qualify_min_const_fn.rs80
-rw-r--r--tests/ui/missing_const_for_fn/cant_be_const.rs4
3 files changed, 50 insertions, 35 deletions
diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs
index 8c883445a79..e600dc58c9d 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -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;
diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs
index a38045cdcd1..cdaf8985d5e 100644
--- a/clippy_utils/src/qualify_min_const_fn.rs
+++ b/clippy_utils/src/qualify_min_const_fn.rs
@@ -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>)>;
@@ -52,14 +59,13 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv)
     }
 
     for local in &body.local_decls {
-        check_ty(tcx, local.ty, local.source_info.span, false)?;
+        check_ty(tcx, local.ty, local.source_info.span)?;
     }
     // impl trait is gone in MIR, so check the return type manually
     check_ty(
         tcx,
         tcx.fn_sig(def_id).subst_identity().output().skip_binder(),
         body.local_decls.iter().next().unwrap().source_info.span,
-        false,
     )?;
 
     for bb in body.basic_blocks.iter() {
@@ -71,7 +77,7 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv)
     Ok(())
 }
 
-fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, in_drop: bool) -> McfResult {
+fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult {
     for arg in ty.walk() {
         let ty = match arg.unpack() {
             GenericArgKind::Type(ty) => ty,
@@ -81,27 +87,6 @@ fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, in_drop: bool) ->
             GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue,
         };
 
-        // Only do this check if we're in `TerminatorKind::Drop`, otherwise rustc will sometimes overflow
-        // its stack. This check is unnecessary outside of a `Drop` anyway so it's faster regardless
-        if in_drop && let ty::Adt(def, subst) = ty.kind() {
-            if def.has_non_const_dtor(tcx) && in_drop {
-                return Err((
-                    span,
-                    "cannot drop locals with a non constant destructor in const fn".into(),
-                ));
-            }
-
-            for fields in def.variants().iter().map(|v| &v.fields) {
-                for field in fields {
-                    check_ty(tcx, field.ty(tcx, subst), span, in_drop)?;
-                }
-            }
-
-            for field in def.all_fields() {
-                check_ty(tcx, field.ty(tcx, subst), span, in_drop)?;
-            }
-        }
-
         match ty.kind() {
             ty::Ref(_, _, hir::Mutability::Mut) => {
                 return Err((span, "mutable references in const fn are unstable".into()));
@@ -288,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 {
@@ -327,20 +313,19 @@ fn check_terminator<'tcx>(
         | TerminatorKind::Resume
         | TerminatorKind::Terminate
         | TerminatorKind::Unreachable => Ok(()),
-
         TerminatorKind::Drop { place, .. } => {
-            for local in &body.local_decls {
-                check_ty(tcx, local.ty, span, true)?;
+            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,
@@ -384,7 +369,6 @@ fn check_terminator<'tcx>(
                 Err((span, "can only call other const fns within const fn".into()))
             }
         },
-
         TerminatorKind::Assert {
             cond,
             expected: _,
@@ -392,7 +376,6 @@ fn check_terminator<'tcx>(
             target: _,
             unwind: _,
         } => check_operand(tcx, cond, span, body),
-
         TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
     }
 }
@@ -406,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('-')
@@ -425,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
+}
diff --git a/tests/ui/missing_const_for_fn/cant_be_const.rs b/tests/ui/missing_const_for_fn/cant_be_const.rs
index dffc23177d1..4fe02a9cef8 100644
--- a/tests/ui/missing_const_for_fn/cant_be_const.rs
+++ b/tests/ui/missing_const_for_fn/cant_be_const.rs
@@ -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 {
@@ -135,3 +135,5 @@ enum A {
 }
 
 fn b(this: A) {}
+
+fn c(this: Vec<u16>) {}