Skip to content

Commit 328532d

Browse files
Gate const drop behind const_destruct feature, and fix const_precise_live_drops post-drop-elaboration check
1 parent 75c9e38 commit 328532d

28 files changed

+366
-109
lines changed

compiler/rustc_const_eval/src/check_consts/check.rs

+26-18
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use rustc_span::{Span, Symbol, sym};
2424
use rustc_trait_selection::traits::{
2525
Obligation, ObligationCause, ObligationCauseCode, ObligationCtxt,
2626
};
27-
use tracing::{debug, instrument, trace};
27+
use tracing::{instrument, trace};
2828

2929
use super::ops::{self, NonConstOp, Status};
3030
use super::qualifs::{self, HasMutInterior, NeedsDrop, NeedsNonConstDrop};
@@ -47,7 +47,7 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> {
4747
/// Returns `true` if `local` is `NeedsDrop` at the given `Location`.
4848
///
4949
/// Only updates the cursor if absolutely necessary
50-
fn needs_drop(
50+
pub(crate) fn needs_drop(
5151
&mut self,
5252
ccx: &'mir ConstCx<'mir, 'tcx>,
5353
local: Local,
@@ -324,6 +324,14 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
324324
}
325325
}
326326

327+
/// Emits an error at the given `span` if an expression cannot be evaluated in the current
328+
/// context. This is meant for use in a post-const-checker pass such as the const precise
329+
/// live drops lint.
330+
pub fn check_op_spanned_post<O: NonConstOp<'tcx>>(mut self, op: O, span: Span) {
331+
self.check_op_spanned(op, span);
332+
assert!(self.secondary_errors.is_empty());
333+
}
334+
327335
fn check_static(&mut self, def_id: DefId, span: Span) {
328336
if self.tcx.is_thread_local_static(def_id) {
329337
self.tcx.dcx().span_bug(span, "tls access is checked in `Rvalue::ThreadLocalRef`");
@@ -868,12 +876,13 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
868876
let mut err_span = self.span;
869877
let ty_of_dropped_place = dropped_place.ty(self.body, self.tcx).ty;
870878

871-
let ty_needs_non_const_drop =
872-
qualifs::NeedsNonConstDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place);
873-
874-
debug!(?ty_of_dropped_place, ?ty_needs_non_const_drop);
875-
876-
if !ty_needs_non_const_drop {
879+
let needs_drop = if let Some(local) = dropped_place.as_local() {
880+
self.qualifs.needs_drop(self.ccx, local, location)
881+
} else {
882+
qualifs::NeedsDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place)
883+
};
884+
// If this type doesn't need a drop at all, then there's nothing to enforce.
885+
if !needs_drop {
877886
return;
878887
}
879888

@@ -882,18 +891,17 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
882891
err_span = self.body.local_decls[local].source_info.span;
883892
self.qualifs.needs_non_const_drop(self.ccx, local, location)
884893
} else {
885-
true
894+
qualifs::NeedsNonConstDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place)
886895
};
887896

888-
if needs_non_const_drop {
889-
self.check_op_spanned(
890-
ops::LiveDrop {
891-
dropped_at: Some(terminator.source_info.span),
892-
dropped_ty: ty_of_dropped_place,
893-
},
894-
err_span,
895-
);
896-
}
897+
self.check_op_spanned(
898+
ops::LiveDrop {
899+
dropped_at: Some(terminator.source_info.span),
900+
dropped_ty: ty_of_dropped_place,
901+
needs_non_const_drop,
902+
},
903+
err_span,
904+
);
897905
}
898906

899907
TerminatorKind::InlineAsm { .. } => self.check_op(ops::InlineAsm),

compiler/rustc_const_eval/src/check_consts/ops.rs

+32-6
Original file line numberDiff line numberDiff line change
@@ -461,15 +461,41 @@ impl<'tcx> NonConstOp<'tcx> for InlineAsm {
461461
pub(crate) struct LiveDrop<'tcx> {
462462
pub dropped_at: Option<Span>,
463463
pub dropped_ty: Ty<'tcx>,
464+
pub needs_non_const_drop: bool,
464465
}
465466
impl<'tcx> NonConstOp<'tcx> for LiveDrop<'tcx> {
467+
fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
468+
if self.needs_non_const_drop {
469+
Status::Forbidden
470+
} else {
471+
Status::Unstable {
472+
gate: sym::const_destruct,
473+
gate_already_checked: false,
474+
safe_to_expose_on_stable: false,
475+
is_function_call: false,
476+
}
477+
}
478+
}
479+
466480
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
467-
ccx.dcx().create_err(errors::LiveDrop {
468-
span,
469-
dropped_ty: self.dropped_ty,
470-
kind: ccx.const_kind(),
471-
dropped_at: self.dropped_at,
472-
})
481+
if self.needs_non_const_drop {
482+
ccx.dcx().create_err(errors::LiveDrop {
483+
span,
484+
dropped_ty: self.dropped_ty,
485+
kind: ccx.const_kind(),
486+
dropped_at: self.dropped_at,
487+
})
488+
} else {
489+
ccx.tcx.sess.create_feature_err(
490+
errors::LiveDrop {
491+
span,
492+
dropped_ty: self.dropped_ty,
493+
kind: ccx.const_kind(),
494+
dropped_at: self.dropped_at,
495+
},
496+
sym::const_destruct,
497+
)
498+
}
473499
}
474500
}
475501

compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs

+34-28
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use rustc_middle::mir::visit::Visitor;
22
use rustc_middle::mir::{self, BasicBlock, Location};
3-
use rustc_middle::ty::{Ty, TyCtxt};
4-
use rustc_span::Span;
3+
use rustc_middle::ty::TyCtxt;
54
use rustc_span::symbol::sym;
65
use tracing::trace;
76

87
use super::ConstCx;
98
use super::check::Qualifs;
10-
use super::ops::{self, NonConstOp};
9+
use super::ops::{self};
1110
use super::qualifs::{NeedsNonConstDrop, Qualif};
11+
use crate::check_consts::check::Checker;
12+
use crate::check_consts::qualifs::NeedsDrop;
1213
use crate::check_consts::rustc_allow_const_fn_unstable;
1314

1415
/// 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> {
6465
}
6566
}
6667

67-
impl<'tcx> CheckLiveDrops<'_, 'tcx> {
68-
fn check_live_drop(&self, span: Span, dropped_ty: Ty<'tcx>) {
69-
ops::LiveDrop { dropped_at: None, dropped_ty }.build_error(self.ccx, span).emit();
70-
}
71-
}
72-
7368
impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
7469
fn visit_basic_block_data(&mut self, bb: BasicBlock, block: &mir::BasicBlockData<'tcx>) {
7570
trace!("visit_basic_block_data: bb={:?} is_cleanup={:?}", bb, block.is_cleanup);
@@ -87,28 +82,39 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
8782

8883
match &terminator.kind {
8984
mir::TerminatorKind::Drop { place: dropped_place, .. } => {
90-
let dropped_ty = dropped_place.ty(self.body, self.tcx).ty;
91-
92-
if !NeedsNonConstDrop::in_any_value_of_ty(self.ccx, dropped_ty) {
93-
// Instead of throwing a bug, we just return here. This is because we have to
94-
// run custom `const Drop` impls.
85+
let ty_of_dropped_place = dropped_place.ty(self.body, self.tcx).ty;
86+
87+
let needs_drop = if let Some(local) = dropped_place.as_local() {
88+
self.qualifs.needs_drop(self.ccx, local, location)
89+
} else {
90+
NeedsDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place)
91+
};
92+
// If this type doesn't need a drop at all, then there's nothing to enforce.
93+
if !needs_drop {
9594
return;
9695
}
9796

98-
if dropped_place.is_indirect() {
99-
self.check_live_drop(terminator.source_info.span, dropped_ty);
100-
return;
101-
}
102-
103-
// Drop elaboration is not precise enough to accept code like
104-
// `tests/ui/consts/control-flow/drop-pass.rs`; e.g., when an `Option<Vec<T>>` is
105-
// initialized with `None` and never changed, it still emits drop glue.
106-
// Hence we additionally check the qualifs here to allow more code to pass.
107-
if self.qualifs.needs_non_const_drop(self.ccx, dropped_place.local, location) {
108-
// Use the span where the dropped local was declared for the error.
109-
let span = self.body.local_decls[dropped_place.local].source_info.span;
110-
self.check_live_drop(span, dropped_ty);
111-
}
97+
let mut err_span = terminator.source_info.span;
98+
99+
let needs_non_const_drop = if let Some(local) = dropped_place.as_local() {
100+
// Use the span where the local was declared as the span of the drop error.
101+
err_span = self.body.local_decls[local].source_info.span;
102+
self.qualifs.needs_non_const_drop(self.ccx, local, location)
103+
} else {
104+
NeedsNonConstDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place)
105+
};
106+
107+
// I know it's not great to be creating a new const checker, but I'd
108+
// rather use it so we can deduplicate the error emitting logic that
109+
// it contains.
110+
Checker::new(self.ccx).check_op_spanned_post(
111+
ops::LiveDrop {
112+
dropped_at: Some(terminator.source_info.span),
113+
dropped_ty: ty_of_dropped_place,
114+
needs_non_const_drop,
115+
},
116+
err_span,
117+
);
112118
}
113119

114120
mir::TerminatorKind::UnwindTerminate(_)

compiler/rustc_const_eval/src/check_consts/qualifs.rs

+27-30
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ pub trait Qualif {
6262
/// It also determines the `Qualif`s for primitive types.
6363
fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool;
6464

65-
/// Returns `true` if the `Qualif` is structural in an ADT's fields, i.e. that we may
66-
/// recurse into an operand if we know what it is.
65+
/// Returns `true` if the `Qualif` is structural in an ADT's fields, i.e. if we may
66+
/// recurse into an operand *value* to determine whether it has this `Qualif`.
6767
///
6868
/// If this returns false, `in_any_value_of_ty` will be invoked to determine the
6969
/// final qualif for this ADT.
70-
fn is_structural_in_adt<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool;
70+
fn is_structural_in_adt_value<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool;
7171
}
7272

7373
/// Constant containing interior mutability (`UnsafeCell<T>`).
@@ -123,7 +123,7 @@ impl Qualif for HasMutInterior {
123123
!errors.is_empty()
124124
}
125125

126-
fn is_structural_in_adt<'tcx>(_cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
126+
fn is_structural_in_adt_value<'tcx>(_cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
127127
// Exactly one type, `UnsafeCell`, has the `HasMutInterior` qualif inherently.
128128
// It arises structurally for all other types.
129129
!adt.is_unsafe_cell()
@@ -140,6 +140,7 @@ pub struct NeedsDrop;
140140
impl Qualif for NeedsDrop {
141141
const ANALYSIS_NAME: &'static str = "flow_needs_drop";
142142
const IS_CLEARED_ON_MOVE: bool = true;
143+
const ALLOW_PROMOTED: bool = true;
143144

144145
fn in_qualifs(qualifs: &ConstQualifs) -> bool {
145146
qualifs.needs_drop
@@ -149,7 +150,7 @@ impl Qualif for NeedsDrop {
149150
ty.needs_drop(cx.tcx, cx.typing_env)
150151
}
151152

152-
fn is_structural_in_adt<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
153+
fn is_structural_in_adt_value<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
153154
!adt.has_dtor(cx.tcx)
154155
}
155156
}
@@ -179,34 +180,30 @@ impl Qualif for NeedsNonConstDrop {
179180
// that the components of this type are also `~const Destruct`. This
180181
// amounts to verifying that there are no values in this ADT that may have
181182
// a non-const drop.
182-
if cx.tcx.features().const_trait_impl() {
183-
let destruct_def_id = cx.tcx.require_lang_item(LangItem::Destruct, Some(cx.body.span));
184-
let infcx =
185-
cx.tcx.infer_ctxt().build(TypingMode::from_param_env(cx.typing_env.param_env));
186-
let ocx = ObligationCtxt::new(&infcx);
187-
ocx.register_obligation(Obligation::new(
188-
cx.tcx,
189-
ObligationCause::misc(cx.body.span, cx.def_id()),
190-
cx.typing_env.param_env,
191-
ty::Binder::dummy(ty::TraitRef::new(cx.tcx, destruct_def_id, [ty]))
192-
.to_host_effect_clause(cx.tcx, match cx.const_kind() {
193-
rustc_hir::ConstContext::ConstFn => ty::BoundConstness::Maybe,
194-
rustc_hir::ConstContext::Static(_)
195-
| rustc_hir::ConstContext::Const { .. } => ty::BoundConstness::Const,
196-
}),
197-
));
198-
!ocx.select_all_or_error().is_empty()
199-
} else {
200-
NeedsDrop::in_any_value_of_ty(cx, ty)
201-
}
183+
let destruct_def_id = cx.tcx.require_lang_item(LangItem::Destruct, Some(cx.body.span));
184+
let infcx = cx.tcx.infer_ctxt().build(TypingMode::from_param_env(cx.typing_env.param_env));
185+
let ocx = ObligationCtxt::new(&infcx);
186+
ocx.register_obligation(Obligation::new(
187+
cx.tcx,
188+
ObligationCause::misc(cx.body.span, cx.def_id()),
189+
cx.typing_env.param_env,
190+
ty::Binder::dummy(ty::TraitRef::new(cx.tcx, destruct_def_id, [ty]))
191+
.to_host_effect_clause(cx.tcx, match cx.const_kind() {
192+
rustc_hir::ConstContext::ConstFn => ty::BoundConstness::Maybe,
193+
rustc_hir::ConstContext::Static(_) | rustc_hir::ConstContext::Const { .. } => {
194+
ty::BoundConstness::Const
195+
}
196+
}),
197+
));
198+
!ocx.select_all_or_error().is_empty()
202199
}
203200

204-
fn is_structural_in_adt<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
201+
fn is_structural_in_adt_value<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
205202
// As soon as an ADT has a destructor, then the drop becomes non-structural
206203
// in its value since:
207-
// 1. The destructor may have `~const` bounds that need to be satisfied on
208-
// top of checking that the components of a specific operand are const-drop.
209-
// While this could be instead satisfied by checking that the `~const Drop`
204+
// 1. The destructor may have `~const` bounds which are not present on the type.
205+
// Someone needs to check that those are satisfied.
206+
// While this could be done instead satisfied by checking that the `~const Drop`
210207
// impl holds (i.e. replicating part of the `in_any_value_of_ty` logic above),
211208
// even in this case, we have another problem, which is,
212209
// 2. The destructor may *modify* the operand being dropped, so even if we
@@ -271,7 +268,7 @@ where
271268
// then we cannot recurse on its fields. Instead,
272269
// we fall back to checking the qualif for *any* value
273270
// of the ADT.
274-
if def.is_union() || !Q::is_structural_in_adt(cx, def) {
271+
if def.is_union() || !Q::is_structural_in_adt_value(cx, def) {
275272
return Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx));
276273
}
277274
}

compiler/rustc_feature/src/unstable.rs

+2
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,8 @@ declare_features! (
424424
(unstable, const_async_blocks, "1.53.0", Some(85368)),
425425
/// Allows `const || {}` closures in const contexts.
426426
(incomplete, const_closures, "1.68.0", Some(106003)),
427+
/// Uwu
428+
(unstable, const_destruct, "CURRENT_RUSTC_VERSION", Some(133214)),
427429
/// Allows `for _ in _` loops in const contexts.
428430
(unstable, const_for, "1.56.0", Some(87575)),
429431
/// Be more precise when looking for live drops in a const context.

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,7 @@ symbols! {
610610
const_compare_raw_pointers,
611611
const_constructor,
612612
const_deallocate,
613+
const_destruct,
613614
const_eval_limit,
614615
const_eval_select,
615616
const_evaluatable_checked,

library/core/src/marker.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,7 @@ marker_impls! {
953953
///
954954
/// This should be used for `~const` bounds,
955955
/// as non-const bounds will always hold for every type.
956-
#[unstable(feature = "const_trait_impl", issue = "67792")]
956+
#[unstable(feature = "const_destruct", issue = "10")]
957957
#[lang = "destruct"]
958958
#[rustc_on_unimplemented(message = "can't drop `{Self}`", append_const_msg)]
959959
#[rustc_deny_explicit_impl(implement_via_object = false)]

tests/ui/consts/const-block-const-bound.stderr

+23-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
error[E0658]: use of unstable library feature `const_destruct`
2+
--> $DIR/const-block-const-bound.rs:6:5
3+
|
4+
LL | use std::marker::Destruct;
5+
| ^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: see issue #10 <https://github.com/rust-lang/rust/issues/10> for more information
8+
= help: add `#![feature(const_destruct)]` to the crate attributes to enable
9+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
10+
11+
error[E0658]: use of unstable library feature `const_destruct`
12+
--> $DIR/const-block-const-bound.rs:8:22
13+
|
14+
LL | const fn f<T: ~const Destruct>(x: T) {}
15+
| ^^^^^^^^
16+
|
17+
= note: see issue #10 <https://github.com/rust-lang/rust/issues/10> for more information
18+
= help: add `#![feature(const_destruct)]` to the crate attributes to enable
19+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
20+
121
error: `~const` can only be applied to `#[const_trait]` traits
222
--> $DIR/const-block-const-bound.rs:8:15
323
|
@@ -20,6 +40,7 @@ LL | const fn f<T: ~const Destruct>(x: T) {}
2040
| |
2141
| the destructor for this type cannot be evaluated in constant functions
2242

23-
error: aborting due to 3 previous errors
43+
error: aborting due to 5 previous errors
2444

25-
For more information about this error, try `rustc --explain E0493`.
45+
Some errors have detailed explanations: E0493, E0658.
46+
For more information about an error, try `rustc --explain E0493`.

0 commit comments

Comments
 (0)