Skip to content

Commit

Permalink
Gate const drop behind const_destruct feature, and fix const_precise_…
Browse files Browse the repository at this point in the history
…live_drops post-drop-elaboration check
  • Loading branch information
compiler-errors committed Nov 12, 2024
1 parent 300d9c5 commit 6ab6f74
Show file tree
Hide file tree
Showing 27 changed files with 338 additions and 102 deletions.
44 changes: 26 additions & 18 deletions compiler/rustc_const_eval/src/check_consts/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,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};
Expand All @@ -46,7 +46,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,
Expand Down Expand Up @@ -314,6 +314,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`");
Expand Down Expand Up @@ -839,12 +847,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;
}

Expand All @@ -853,18 +862,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),
Expand Down
37 changes: 31 additions & 6 deletions compiler/rustc_const_eval/src/check_consts/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,15 +452,40 @@ 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,
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,
)
}
}
}

Expand Down
62 changes: 34 additions & 28 deletions compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -66,12 +67,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);
Expand All @@ -89,28 +84,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(_)
Expand Down
36 changes: 17 additions & 19 deletions compiler/rustc_const_eval/src/check_consts/qualifs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,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
Expand Down Expand Up @@ -175,25 +176,22 @@ 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.param_env));
let ocx = ObligationCtxt::new(&infcx);
ocx.register_obligation(Obligation::new(
cx.tcx,
ObligationCause::misc(cx.body.span, cx.def_id()),
cx.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 = cx.tcx.infer_ctxt().build(TypingMode::from_param_env(cx.param_env));
let ocx = ObligationCtxt::new(&infcx);
ocx.register_obligation(Obligation::new(
cx.tcx,
ObligationCause::misc(cx.body.span, cx.def_id()),
cx.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 {
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,8 @@ declare_features! (
(unstable, const_async_blocks, "1.53.0", Some(85368)),
/// Allows `const || {}` closures in const contexts.
(incomplete, const_closures, "1.68.0", Some(106003)),
/// Uwu
(unstable, const_destruct, "CURRENT_RUSTC_VERSION", Some(10)),
/// Allows `for _ in _` loops in const contexts.
(unstable, const_for, "1.56.0", Some(87575)),
/// Be more precise when looking for live drops in a const context.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ symbols! {
const_compare_raw_pointers,
const_constructor,
const_deallocate,
const_destruct,
const_eval_limit,
const_eval_select,
const_evaluatable_checked,
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/marker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,7 @@ marker_impls! {
///
/// This should be used for `~const` bounds,
/// as non-const bounds will always hold for every type.
#[unstable(feature = "const_trait_impl", issue = "67792")]
#[unstable(feature = "const_destruct", issue = "10")]
#[lang = "destruct"]
#[rustc_on_unimplemented(message = "can't drop `{Self}`", append_const_msg)]
#[rustc_deny_explicit_impl(implement_via_object = false)]
Expand Down
25 changes: 23 additions & 2 deletions tests/ui/consts/const-block-const-bound.stderr
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
error[E0658]: use of unstable library feature `const_destruct`
--> $DIR/const-block-const-bound.rs:6:5
|
LL | use std::marker::Destruct;
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #10 <https://github.com/rust-lang/rust/issues/10> for more information
= help: add `#![feature(const_destruct)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date

error[E0658]: use of unstable library feature `const_destruct`
--> $DIR/const-block-const-bound.rs:8:22
|
LL | const fn f<T: ~const Destruct>(x: T) {}
| ^^^^^^^^
|
= note: see issue #10 <https://github.com/rust-lang/rust/issues/10> for more information
= help: add `#![feature(const_destruct)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date

error: `~const` can only be applied to `#[const_trait]` traits
--> $DIR/const-block-const-bound.rs:8:15
|
Expand All @@ -20,6 +40,7 @@ LL | const fn f<T: ~const Destruct>(x: T) {}
| |
| the destructor for this type cannot be evaluated in constant functions

error: aborting due to 3 previous errors
error: aborting due to 5 previous errors

For more information about this error, try `rustc --explain E0493`.
Some errors have detailed explanations: E0493, E0658.
For more information about an error, try `rustc --explain E0493`.
10 changes: 8 additions & 2 deletions tests/ui/consts/control-flow/drop-fail.precise.stderr
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
error[E0493]: destructor of `Option<Vec<i32>>` cannot be evaluated at compile-time
--> $DIR/drop-fail.rs:8:9
--> $DIR/drop-fail.rs:9:9
|
LL | let x = Some(Vec::new());
| ^ the destructor for this type cannot be evaluated in constants
...
LL | };
| - value is dropped here

error[E0493]: destructor of `Option<Vec<i32>>` cannot be evaluated at compile-time
--> $DIR/drop-fail.rs:39:9
--> $DIR/drop-fail.rs:40:9
|
LL | let mut tmp = None;
| ^^^^^^^ the destructor for this type cannot be evaluated in constants
...
LL | };
| - value is dropped here

error: aborting due to 2 previous errors

Expand Down
1 change: 1 addition & 0 deletions tests/ui/consts/control-flow/drop-fail.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//@ revisions: stock precise

#![feature(const_destruct)]
#![cfg_attr(precise, feature(const_precise_live_drops))]

// `x` is *not* always moved into the final value and may be dropped inside the initializer.
Expand Down
8 changes: 4 additions & 4 deletions tests/ui/consts/control-flow/drop-fail.stock.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0493]: destructor of `Option<Vec<i32>>` cannot be evaluated at compile-time
--> $DIR/drop-fail.rs:8:9
--> $DIR/drop-fail.rs:9:9
|
LL | let x = Some(Vec::new());
| ^ the destructor for this type cannot be evaluated in constants
Expand All @@ -8,7 +8,7 @@ LL | };
| - value is dropped here

error[E0493]: destructor of `(Vec<i32>,)` cannot be evaluated at compile-time
--> $DIR/drop-fail.rs:21:9
--> $DIR/drop-fail.rs:22:9
|
LL | let vec_tuple = (Vec::new(),);
| ^^^^^^^^^ the destructor for this type cannot be evaluated in constants
Expand All @@ -17,7 +17,7 @@ LL | };
| - value is dropped here

error[E0493]: destructor of `Result<Vec<i32>, Vec<i32>>` cannot be evaluated at compile-time
--> $DIR/drop-fail.rs:29:9
--> $DIR/drop-fail.rs:30:9
|
LL | let x: Result<_, Vec<i32>> = Ok(Vec::new());
| ^ the destructor for this type cannot be evaluated in constants
Expand All @@ -26,7 +26,7 @@ LL | };
| - value is dropped here

error[E0493]: destructor of `Option<Vec<i32>>` cannot be evaluated at compile-time
--> $DIR/drop-fail.rs:39:9
--> $DIR/drop-fail.rs:40:9
|
LL | let mut tmp = None;
| ^^^^^^^ the destructor for this type cannot be evaluated in constants
Expand Down
Loading

0 comments on commit 6ab6f74

Please sign in to comment.