about summary refs log tree commit diff
path: root/compiler/rustc_const_eval/src
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2024-11-19 17:08:52 +0000
committerMichael Goulet <michael@errs.io>2024-11-22 16:54:40 +0000
commit20882608529a969bd878ad787cf0038716c021df (patch)
tree2fcb9b0c14a63b6dac1975c57c86294d33a06552 /compiler/rustc_const_eval/src
parentb75c1c3dd64d6846d670adca10ebd3735b48dc6a (diff)
downloadrust-20882608529a969bd878ad787cf0038716c021df.tar.gz
rust-20882608529a969bd878ad787cf0038716c021df.zip
Gate const drop behind const_destruct feature, and fix const_precise_live_drops post-drop-elaboration check
Diffstat (limited to 'compiler/rustc_const_eval/src')
-rw-r--r--compiler/rustc_const_eval/src/check_consts/check.rs44
-rw-r--r--compiler/rustc_const_eval/src/check_consts/ops.rs38
-rw-r--r--compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs62
-rw-r--r--compiler/rustc_const_eval/src/check_consts/qualifs.rs57
4 files changed, 119 insertions, 82 deletions
diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs
index 1129b5caec5..a088a210e26 100644
--- a/compiler/rustc_const_eval/src/check_consts/check.rs
+++ b/compiler/rustc_const_eval/src/check_consts/check.rs
@@ -24,7 +24,7 @@ use rustc_span::{Span, Symbol, sym};
 use rustc_trait_selection::traits::{
     Obligation, ObligationCause, ObligationCauseCode, ObligationCtxt,
 };
-use tracing::{debug, instrument, trace};
+use tracing::{instrument, trace};
 
 use super::ops::{self, NonConstOp, Status};
 use super::qualifs::{self, HasMutInterior, NeedsDrop, NeedsNonConstDrop};
@@ -47,7 +47,7 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
     /// Returns `true` if `local` is `NeedsDrop` at the given `Location`.
     ///
     /// Only updates the cursor if absolutely necessary
-    fn needs_drop(
+    pub(crate) fn needs_drop(
         &mut self,
         ccx: &'mir ConstCx<'mir, 'tcx>,
         local: Local,
@@ -324,6 +324,14 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
         }
     }
 
+    /// Emits an error at the given `span` if an expression cannot be evaluated in the current
+    /// context. This is meant for use in a post-const-checker pass such as the const precise
+    /// live drops lint.
+    pub fn check_op_spanned_post<O: NonConstOp<'tcx>>(mut self, op: O, span: Span) {
+        self.check_op_spanned(op, span);
+        assert!(self.secondary_errors.is_empty());
+    }
+
     fn check_static(&mut self, def_id: DefId, span: Span) {
         if self.tcx.is_thread_local_static(def_id) {
             self.tcx.dcx().span_bug(span, "tls access is checked in `Rvalue::ThreadLocalRef`");
@@ -869,12 +877,13 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                 let mut err_span = self.span;
                 let ty_of_dropped_place = dropped_place.ty(self.body, self.tcx).ty;
 
-                let ty_needs_non_const_drop =
-                    qualifs::NeedsNonConstDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place);
-
-                debug!(?ty_of_dropped_place, ?ty_needs_non_const_drop);
-
-                if !ty_needs_non_const_drop {
+                let needs_drop = if let Some(local) = dropped_place.as_local() {
+                    self.qualifs.needs_drop(self.ccx, local, location)
+                } else {
+                    qualifs::NeedsDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place)
+                };
+                // If this type doesn't need a drop at all, then there's nothing to enforce.
+                if !needs_drop {
                     return;
                 }
 
@@ -883,18 +892,17 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                     err_span = self.body.local_decls[local].source_info.span;
                     self.qualifs.needs_non_const_drop(self.ccx, local, location)
                 } else {
-                    true
+                    qualifs::NeedsNonConstDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place)
                 };
 
-                if needs_non_const_drop {
-                    self.check_op_spanned(
-                        ops::LiveDrop {
-                            dropped_at: Some(terminator.source_info.span),
-                            dropped_ty: ty_of_dropped_place,
-                        },
-                        err_span,
-                    );
-                }
+                self.check_op_spanned(
+                    ops::LiveDrop {
+                        dropped_at: Some(terminator.source_info.span),
+                        dropped_ty: ty_of_dropped_place,
+                        needs_non_const_drop,
+                    },
+                    err_span,
+                );
             }
 
             TerminatorKind::InlineAsm { .. } => self.check_op(ops::InlineAsm),
diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs
index 8ba6b89aad4..c8d6358161f 100644
--- a/compiler/rustc_const_eval/src/check_consts/ops.rs
+++ b/compiler/rustc_const_eval/src/check_consts/ops.rs
@@ -461,15 +461,41 @@ impl<'tcx> NonConstOp<'tcx> for InlineAsm {
 pub(crate) struct LiveDrop<'tcx> {
     pub dropped_at: Option<Span>,
     pub dropped_ty: Ty<'tcx>,
+    pub needs_non_const_drop: bool,
 }
 impl<'tcx> NonConstOp<'tcx> for LiveDrop<'tcx> {
+    fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
+        if self.needs_non_const_drop {
+            Status::Forbidden
+        } else {
+            Status::Unstable {
+                gate: sym::const_destruct,
+                gate_already_checked: false,
+                safe_to_expose_on_stable: false,
+                is_function_call: false,
+            }
+        }
+    }
+
     fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
-        ccx.dcx().create_err(errors::LiveDrop {
-            span,
-            dropped_ty: self.dropped_ty,
-            kind: ccx.const_kind(),
-            dropped_at: self.dropped_at,
-        })
+        if self.needs_non_const_drop {
+            ccx.dcx().create_err(errors::LiveDrop {
+                span,
+                dropped_ty: self.dropped_ty,
+                kind: ccx.const_kind(),
+                dropped_at: self.dropped_at,
+            })
+        } else {
+            ccx.tcx.sess.create_feature_err(
+                errors::LiveDrop {
+                    span,
+                    dropped_ty: self.dropped_ty,
+                    kind: ccx.const_kind(),
+                    dropped_at: self.dropped_at,
+                },
+                sym::const_destruct,
+            )
+        }
     }
 }
 
diff --git a/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs b/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs
index f6eb130fbd3..c3a9aad6421 100644
--- a/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs
+++ b/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs
@@ -1,14 +1,15 @@
 use rustc_middle::mir::visit::Visitor;
 use rustc_middle::mir::{self, BasicBlock, Location};
-use rustc_middle::ty::{Ty, TyCtxt};
-use rustc_span::Span;
+use rustc_middle::ty::TyCtxt;
 use rustc_span::symbol::sym;
 use tracing::trace;
 
 use super::ConstCx;
 use super::check::Qualifs;
-use super::ops::{self, NonConstOp};
+use super::ops::{self};
 use super::qualifs::{NeedsNonConstDrop, Qualif};
+use crate::check_consts::check::Checker;
+use crate::check_consts::qualifs::NeedsDrop;
 use crate::check_consts::rustc_allow_const_fn_unstable;
 
 /// Returns `true` if we should use the more precise live drop checker that runs after drop
@@ -64,12 +65,6 @@ impl<'mir, 'tcx> std::ops::Deref for CheckLiveDrops<'mir, 'tcx> {
     }
 }
 
-impl<'tcx> CheckLiveDrops<'_, 'tcx> {
-    fn check_live_drop(&self, span: Span, dropped_ty: Ty<'tcx>) {
-        ops::LiveDrop { dropped_at: None, dropped_ty }.build_error(self.ccx, span).emit();
-    }
-}
-
 impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
     fn visit_basic_block_data(&mut self, bb: BasicBlock, block: &mir::BasicBlockData<'tcx>) {
         trace!("visit_basic_block_data: bb={:?} is_cleanup={:?}", bb, block.is_cleanup);
@@ -87,28 +82,39 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
 
         match &terminator.kind {
             mir::TerminatorKind::Drop { place: dropped_place, .. } => {
-                let dropped_ty = dropped_place.ty(self.body, self.tcx).ty;
-
-                if !NeedsNonConstDrop::in_any_value_of_ty(self.ccx, dropped_ty) {
-                    // Instead of throwing a bug, we just return here. This is because we have to
-                    // run custom `const Drop` impls.
+                let ty_of_dropped_place = dropped_place.ty(self.body, self.tcx).ty;
+
+                let needs_drop = if let Some(local) = dropped_place.as_local() {
+                    self.qualifs.needs_drop(self.ccx, local, location)
+                } else {
+                    NeedsDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place)
+                };
+                // If this type doesn't need a drop at all, then there's nothing to enforce.
+                if !needs_drop {
                     return;
                 }
 
-                if dropped_place.is_indirect() {
-                    self.check_live_drop(terminator.source_info.span, dropped_ty);
-                    return;
-                }
-
-                // Drop elaboration is not precise enough to accept code like
-                // `tests/ui/consts/control-flow/drop-pass.rs`; e.g., when an `Option<Vec<T>>` is
-                // initialized with `None` and never changed, it still emits drop glue.
-                // Hence we additionally check the qualifs here to allow more code to pass.
-                if self.qualifs.needs_non_const_drop(self.ccx, dropped_place.local, location) {
-                    // Use the span where the dropped local was declared for the error.
-                    let span = self.body.local_decls[dropped_place.local].source_info.span;
-                    self.check_live_drop(span, dropped_ty);
-                }
+                let mut err_span = terminator.source_info.span;
+
+                let needs_non_const_drop = if let Some(local) = dropped_place.as_local() {
+                    // Use the span where the local was declared as the span of the drop error.
+                    err_span = self.body.local_decls[local].source_info.span;
+                    self.qualifs.needs_non_const_drop(self.ccx, local, location)
+                } else {
+                    NeedsNonConstDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place)
+                };
+
+                // I know it's not great to be creating a new const checker, but I'd
+                // rather use it so we can deduplicate the error emitting logic that
+                // it contains.
+                Checker::new(self.ccx).check_op_spanned_post(
+                    ops::LiveDrop {
+                        dropped_at: Some(terminator.source_info.span),
+                        dropped_ty: ty_of_dropped_place,
+                        needs_non_const_drop,
+                    },
+                    err_span,
+                );
             }
 
             mir::TerminatorKind::UnwindTerminate(_)
diff --git a/compiler/rustc_const_eval/src/check_consts/qualifs.rs b/compiler/rustc_const_eval/src/check_consts/qualifs.rs
index df19ff2c16a..29fd3aa7d4d 100644
--- a/compiler/rustc_const_eval/src/check_consts/qualifs.rs
+++ b/compiler/rustc_const_eval/src/check_consts/qualifs.rs
@@ -62,12 +62,12 @@ pub trait Qualif {
     /// It also determines the `Qualif`s for primitive types.
     fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool;
 
-    /// Returns `true` if the `Qualif` is structural in an ADT's fields, i.e. that we may
-    /// recurse into an operand if we know what it is.
+    /// Returns `true` if the `Qualif` is structural in an ADT's fields, i.e. if we may
+    /// recurse into an operand *value* to determine whether it has this `Qualif`.
     ///
     /// If this returns false, `in_any_value_of_ty` will be invoked to determine the
     /// final qualif for this ADT.
-    fn is_structural_in_adt<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool;
+    fn is_structural_in_adt_value<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool;
 }
 
 /// Constant containing interior mutability (`UnsafeCell<T>`).
@@ -123,7 +123,7 @@ impl Qualif for HasMutInterior {
         !errors.is_empty()
     }
 
-    fn is_structural_in_adt<'tcx>(_cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
+    fn is_structural_in_adt_value<'tcx>(_cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
         // Exactly one type, `UnsafeCell`, has the `HasMutInterior` qualif inherently.
         // It arises structurally for all other types.
         !adt.is_unsafe_cell()
@@ -140,6 +140,7 @@ pub struct NeedsDrop;
 impl Qualif for NeedsDrop {
     const ANALYSIS_NAME: &'static str = "flow_needs_drop";
     const IS_CLEARED_ON_MOVE: bool = true;
+    const ALLOW_PROMOTED: bool = true;
 
     fn in_qualifs(qualifs: &ConstQualifs) -> bool {
         qualifs.needs_drop
@@ -149,7 +150,7 @@ impl Qualif for NeedsDrop {
         ty.needs_drop(cx.tcx, cx.typing_env)
     }
 
-    fn is_structural_in_adt<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
+    fn is_structural_in_adt_value<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
         !adt.has_dtor(cx.tcx)
     }
 }
@@ -179,34 +180,30 @@ impl Qualif for NeedsNonConstDrop {
         // that the components of this type are also `~const Destruct`. This
         // amounts to verifying that there are no values in this ADT that may have
         // a non-const drop.
-        if cx.tcx.features().const_trait_impl() {
-            let destruct_def_id = cx.tcx.require_lang_item(LangItem::Destruct, Some(cx.body.span));
-            let infcx =
-                cx.tcx.infer_ctxt().build(TypingMode::from_param_env(cx.typing_env.param_env));
-            let ocx = ObligationCtxt::new(&infcx);
-            ocx.register_obligation(Obligation::new(
-                cx.tcx,
-                ObligationCause::misc(cx.body.span, cx.def_id()),
-                cx.typing_env.param_env,
-                ty::Binder::dummy(ty::TraitRef::new(cx.tcx, destruct_def_id, [ty]))
-                    .to_host_effect_clause(cx.tcx, match cx.const_kind() {
-                        rustc_hir::ConstContext::ConstFn => ty::BoundConstness::Maybe,
-                        rustc_hir::ConstContext::Static(_)
-                        | rustc_hir::ConstContext::Const { .. } => ty::BoundConstness::Const,
-                    }),
-            ));
-            !ocx.select_all_or_error().is_empty()
-        } else {
-            NeedsDrop::in_any_value_of_ty(cx, ty)
-        }
+        let destruct_def_id = cx.tcx.require_lang_item(LangItem::Destruct, Some(cx.body.span));
+        let (infcx, param_env) = cx.tcx.infer_ctxt().build_with_typing_env(cx.typing_env);
+        let ocx = ObligationCtxt::new(&infcx);
+        ocx.register_obligation(Obligation::new(
+            cx.tcx,
+            ObligationCause::misc(cx.body.span, cx.def_id()),
+            param_env,
+            ty::Binder::dummy(ty::TraitRef::new(cx.tcx, destruct_def_id, [ty]))
+                .to_host_effect_clause(cx.tcx, match cx.const_kind() {
+                    rustc_hir::ConstContext::ConstFn => ty::BoundConstness::Maybe,
+                    rustc_hir::ConstContext::Static(_) | rustc_hir::ConstContext::Const { .. } => {
+                        ty::BoundConstness::Const
+                    }
+                }),
+        ));
+        !ocx.select_all_or_error().is_empty()
     }
 
-    fn is_structural_in_adt<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
+    fn is_structural_in_adt_value<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
         // As soon as an ADT has a destructor, then the drop becomes non-structural
         // in its value since:
-        // 1. The destructor may have `~const` bounds that need to be satisfied on
-        //   top of checking that the components of a specific operand are const-drop.
-        //   While this could be instead satisfied by checking that the `~const Drop`
+        // 1. The destructor may have `~const` bounds which are not present on the type.
+        //   Someone needs to check that those are satisfied.
+        //   While this could be done instead satisfied by checking that the `~const Drop`
         //   impl holds (i.e. replicating part of the `in_any_value_of_ty` logic above),
         //   even in this case, we have another problem, which is,
         // 2. The destructor may *modify* the operand being dropped, so even if we
@@ -271,7 +268,7 @@ where
                 // then we cannot recurse on its fields. Instead,
                 // we fall back to checking the qualif for *any* value
                 // of the ADT.
-                if def.is_union() || !Q::is_structural_in_adt(cx, def) {
+                if def.is_union() || !Q::is_structural_in_adt_value(cx, def) {
                     return Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx));
                 }
             }