Skip to content

Commit 59408ad

Browse files
Implement ~const Destruct in new solver
1 parent a7d9ebd commit 59408ad

File tree

16 files changed

+266
-74
lines changed

16 files changed

+266
-74
lines changed

compiler/rustc_const_eval/src/check_consts/qualifs.rs

+42-39
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
//!
33
//! See the `Qualif` trait for more info.
44
5+
// FIXME(const_trait_impl): This API should be really reworked. It's dangerously general for
6+
// having basically only two use-cases that act in different ways.
7+
58
use rustc_errors::ErrorGuaranteed;
69
use rustc_hir::LangItem;
710
use rustc_infer::infer::TyCtxtInferExt;
811
use rustc_middle::mir::*;
9-
use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty};
12+
use rustc_middle::ty::{self, AdtDef, Ty};
1013
use rustc_middle::{bug, mir};
1114
use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt};
1215
use tracing::instrument;
@@ -59,19 +62,9 @@ pub trait Qualif {
5962
/// It also determines the `Qualif`s for primitive types.
6063
fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool;
6164

62-
/// Returns `true` if this `Qualif` is inherent to the given struct or enum.
63-
///
64-
/// By default, `Qualif`s propagate into ADTs in a structural way: An ADT only becomes
65-
/// qualified if part of it is assigned a value with that `Qualif`. However, some ADTs *always*
66-
/// have a certain `Qualif`, regardless of whether their fields have it. For example, a type
67-
/// with a custom `Drop` impl is inherently `NeedsDrop`.
68-
///
69-
/// Returning `true` for `in_adt_inherently` but `false` for `in_any_value_of_ty` is unsound.
70-
fn in_adt_inherently<'tcx>(
71-
cx: &ConstCx<'_, 'tcx>,
72-
adt: AdtDef<'tcx>,
73-
args: GenericArgsRef<'tcx>,
74-
) -> bool;
65+
/// Returns `true` if the `Qualif` is not structural, i.e. that we should not recurse
66+
/// into the operand.
67+
fn is_non_structural<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool;
7568

7669
/// Returns `true` if this `Qualif` behaves sructurally for pointers and references:
7770
/// the pointer/reference qualifies if and only if the pointee qualifies.
@@ -101,6 +94,11 @@ impl Qualif for HasMutInterior {
10194
return false;
10295
}
10396

97+
// Avoid selecting for `UnsafeCell` either.
98+
if ty.ty_adt_def().is_some_and(|adt| adt.is_unsafe_cell()) {
99+
return true;
100+
}
101+
104102
// We do not use `ty.is_freeze` here, because that requires revealing opaque types, which
105103
// requires borrowck, which in turn will invoke mir_const_qualifs again, causing a cycle error.
106104
// Instead we invoke an obligation context manually, and provide the opaque type inference settings
@@ -129,11 +127,7 @@ impl Qualif for HasMutInterior {
129127
!errors.is_empty()
130128
}
131129

132-
fn in_adt_inherently<'tcx>(
133-
_cx: &ConstCx<'_, 'tcx>,
134-
adt: AdtDef<'tcx>,
135-
_: GenericArgsRef<'tcx>,
136-
) -> bool {
130+
fn is_non_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
137131
// Exactly one type, `UnsafeCell`, has the `HasMutInterior` qualif inherently.
138132
// It arises structurally for all other types.
139133
adt.is_unsafe_cell()
@@ -144,6 +138,7 @@ impl Qualif for HasMutInterior {
144138
}
145139
}
146140

141+
// FIXME(const_trait_impl): Get rid of this!
147142
/// Constant containing an ADT that implements `Drop`.
148143
/// This must be ruled out because implicit promotion would remove side-effects
149144
/// that occur as part of dropping that value. N.B., the implicit promotion has
@@ -163,11 +158,7 @@ impl Qualif for NeedsDrop {
163158
ty.needs_drop(cx.tcx, cx.typing_env)
164159
}
165160

166-
fn in_adt_inherently<'tcx>(
167-
cx: &ConstCx<'_, 'tcx>,
168-
adt: AdtDef<'tcx>,
169-
_: GenericArgsRef<'tcx>,
170-
) -> bool {
161+
fn is_non_structural<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
171162
adt.has_dtor(cx.tcx)
172163
}
173164

@@ -196,16 +187,32 @@ impl Qualif for NeedsNonConstDrop {
196187
return false;
197188
}
198189

199-
// FIXME(const_trait_impl): Reimplement const drop checking.
200-
NeedsDrop::in_any_value_of_ty(cx, ty)
190+
if cx.tcx.features().const_trait_impl() {
191+
let destruct_def_id = cx.tcx.require_lang_item(LangItem::Destruct, Some(cx.body.span));
192+
let infcx = cx.tcx.infer_ctxt().build(TypingMode::from_param_env(cx.param_env));
193+
let ocx = ObligationCtxt::new(&infcx);
194+
ocx.register_obligation(Obligation::new(
195+
cx.tcx,
196+
ObligationCause::misc(cx.body.span, cx.def_id()),
197+
cx.param_env,
198+
ty::Binder::dummy(ty::TraitRef::new(cx.tcx, destruct_def_id, [ty]))
199+
.to_host_effect_clause(cx.tcx, match cx.const_kind() {
200+
rustc_hir::ConstContext::ConstFn => ty::BoundConstness::Maybe,
201+
rustc_hir::ConstContext::Static(_)
202+
| rustc_hir::ConstContext::Const { .. } => ty::BoundConstness::Const,
203+
}),
204+
));
205+
!ocx.select_all_or_error().is_empty()
206+
} else {
207+
NeedsDrop::in_any_value_of_ty(cx, ty)
208+
}
201209
}
202210

203-
fn in_adt_inherently<'tcx>(
204-
cx: &ConstCx<'_, 'tcx>,
205-
adt: AdtDef<'tcx>,
206-
_: GenericArgsRef<'tcx>,
207-
) -> bool {
208-
adt.has_non_const_dtor(cx.tcx)
211+
fn is_non_structural<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
212+
// Even a `const` dtor may have `~const` bounds that may need to
213+
// be satisfied, so this becomes non-structural as soon as the
214+
// ADT gets a destructor at all.
215+
adt.has_dtor(cx.tcx)
209216
}
210217

211218
fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool {
@@ -261,14 +268,10 @@ where
261268
Rvalue::Aggregate(kind, operands) => {
262269
// Return early if we know that the struct or enum being constructed is always
263270
// qualified.
264-
if let AggregateKind::Adt(adt_did, _, args, ..) = **kind {
271+
if let AggregateKind::Adt(adt_did, ..) = **kind {
265272
let def = cx.tcx.adt_def(adt_did);
266-
if Q::in_adt_inherently(cx, def, args) {
267-
return true;
268-
}
269-
// Don't do any value-based reasoning for unions.
270-
if def.is_union() && Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx)) {
271-
return true;
273+
if def.is_union() || Q::is_non_structural(cx, def) {
274+
return Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx));
272275
}
273276
}
274277

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -269,13 +269,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
269269
);
270270
}
271271
Adjust::Deref(None) => {
272-
// FIXME(effects): We *could* enforce `&T: ~const Deref` here.
272+
// FIXME(const_trait_impl): We *could* enforce `&T: ~const Deref` here.
273273
}
274274
Adjust::Pointer(_pointer_coercion) => {
275-
// FIXME(effects): We should probably enforce these.
275+
// FIXME(const_trait_impl): We should probably enforce these.
276276
}
277277
Adjust::ReborrowPin(_mutability) => {
278-
// FIXME(effects): We could enforce these; they correspond to
278+
// FIXME(const_trait_impl): We could enforce these; they correspond to
279279
// `&mut T: DerefMut` tho, so it's kinda moot.
280280
}
281281
Adjust::Borrow(_) => {

compiler/rustc_middle/src/ty/adt.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use rustc_macros::{HashStable, TyDecodable, TyEncodable};
1818
use rustc_query_system::ich::StableHashingContext;
1919
use rustc_session::DataTypeKind;
2020
use rustc_span::symbol::sym;
21+
use rustc_type_ir::solve::AdtDestructorKind;
2122
use tracing::{debug, info, trace};
2223

2324
use super::{
@@ -232,6 +233,13 @@ impl<'tcx> rustc_type_ir::inherent::AdtDef<TyCtxt<'tcx>> for AdtDef<'tcx> {
232233
fn is_fundamental(self) -> bool {
233234
self.is_fundamental()
234235
}
236+
237+
fn destructor(self, tcx: TyCtxt<'tcx>) -> Option<AdtDestructorKind> {
238+
Some(match self.destructor(tcx)?.constness {
239+
hir::Constness::Const => AdtDestructorKind::Const,
240+
hir::Constness::NotConst => AdtDestructorKind::NotConst,
241+
})
242+
}
235243
}
236244

237245
#[derive(Copy, Clone, Debug, Eq, PartialEq, HashStable, TyEncodable, TyDecodable)]
@@ -402,10 +410,6 @@ impl<'tcx> AdtDef<'tcx> {
402410
self.destructor(tcx).is_some()
403411
}
404412

405-
pub fn has_non_const_dtor(self, tcx: TyCtxt<'tcx>) -> bool {
406-
matches!(self.destructor(tcx), Some(Destructor { constness: hir::Constness::NotConst, .. }))
407-
}
408-
409413
/// Asserts this is a struct or union and returns its unique variant.
410414
pub fn non_enum_variant(self) -> &'tcx VariantDef {
411415
assert!(self.is_struct() || self.is_union());

compiler/rustc_middle/src/ty/context.rs

+5
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
384384
self.is_conditionally_const(def_id)
385385
}
386386

387+
fn alias_has_const_conditions(self, def_id: DefId) -> bool {
388+
self.is_conditionally_const(def_id)
389+
}
390+
387391
fn const_conditions(
388392
self,
389393
def_id: DefId,
@@ -663,6 +667,7 @@ bidirectional_lang_item_map! {
663667
CoroutineYield,
664668
Destruct,
665669
DiscriminantKind,
670+
Drop,
666671
DynMetadata,
667672
Fn,
668673
FnMut,

compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs

+73-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_type_ir_macros::{TypeFoldable_Generic, TypeVisitable_Generic};
1212
use tracing::instrument;
1313

1414
use crate::delegate::SolverDelegate;
15-
use crate::solve::{EvalCtxt, Goal, NoSolution};
15+
use crate::solve::{AdtDestructorKind, EvalCtxt, Goal, NoSolution};
1616

1717
// Calculates the constituent types of a type for `auto trait` purposes.
1818
#[instrument(level = "trace", skip(ecx), ret)]
@@ -703,6 +703,78 @@ pub(in crate::solve) fn extract_fn_def_from_const_callable<I: Interner>(
703703
}
704704
}
705705

706+
pub(in crate::solve) fn const_conditions_for_destruct<I: Interner>(
707+
cx: I,
708+
self_ty: I::Ty,
709+
) -> Result<Vec<ty::TraitRef<I>>, NoSolution> {
710+
let destruct_def_id = cx.require_lang_item(TraitSolverLangItem::Destruct);
711+
712+
match self_ty.kind() {
713+
// An ADT is `~const Destruct` only if all of the fields are,
714+
// *and* if there is a `Drop` impl, that `Drop` impl is also `~const`.
715+
ty::Adt(adt_def, args) => {
716+
let mut const_conditions: Vec<_> = adt_def
717+
.all_field_tys(cx)
718+
.iter_instantiated(cx, args)
719+
.map(|field_ty| ty::TraitRef::new(cx, destruct_def_id, [field_ty]))
720+
.collect();
721+
match adt_def.destructor(cx) {
722+
// `Drop` impl exists, but it's not const. Type cannot be `~const Destruct`.
723+
Some(AdtDestructorKind::NotConst) => return Err(NoSolution),
724+
// `Drop` impl exists, and it's const. Require `Ty: ~const Drop` to hold.
725+
Some(AdtDestructorKind::Const) => {
726+
let drop_def_id = cx.require_lang_item(TraitSolverLangItem::Drop);
727+
let drop_trait_ref = ty::TraitRef::new(cx, drop_def_id, [self_ty]);
728+
const_conditions.push(drop_trait_ref);
729+
}
730+
// No `Drop` impl, no need to require anything else.
731+
None => {}
732+
}
733+
Ok(const_conditions)
734+
}
735+
736+
ty::Array(ty, _) | ty::Pat(ty, _) | ty::Slice(ty) => {
737+
Ok(vec![ty::TraitRef::new(cx, destruct_def_id, [ty])])
738+
}
739+
740+
ty::Tuple(tys) => Ok(tys
741+
.iter()
742+
.map(|field_ty| ty::TraitRef::new(cx, destruct_def_id, [field_ty]))
743+
.collect()),
744+
745+
// Trivially implement `~const Destruct`
746+
ty::Bool
747+
| ty::Char
748+
| ty::Int(..)
749+
| ty::Uint(..)
750+
| ty::Float(..)
751+
| ty::Str
752+
| ty::RawPtr(..)
753+
| ty::Ref(..)
754+
| ty::FnDef(..)
755+
| ty::FnPtr(..)
756+
| ty::Never
757+
| ty::Infer(ty::InferTy::FloatVar(_) | ty::InferTy::IntVar(_))
758+
| ty::Error(_) => Ok(vec![]),
759+
760+
// Coroutines and closures could implement `~const Drop`,
761+
// but they don't really need to right now.
762+
ty::Closure(_, _)
763+
| ty::CoroutineClosure(_, _)
764+
| ty::Coroutine(_, _)
765+
| ty::CoroutineWitness(_, _) => Err(NoSolution),
766+
767+
ty::Dynamic(..) | ty::Param(_) | ty::Alias(..) | ty::Placeholder(_) | ty::Foreign(_) => {
768+
Err(NoSolution)
769+
}
770+
771+
ty::Bound(..)
772+
| ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
773+
panic!("unexpected type `{self_ty:?}`")
774+
}
775+
}
776+
}
777+
706778
/// Assemble a list of predicates that would be present on a theoretical
707779
/// user impl for an object type. These predicates must be checked any time
708780
/// we assemble a built-in object candidate for an object type, since they

compiler/rustc_next_trait_solver/src/solve/effect_goals.rs

+24-3
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ where
8484
let cx = ecx.cx();
8585
let mut candidates = vec![];
8686

87+
if !ecx.cx().alias_has_const_conditions(alias_ty.def_id) {
88+
return vec![];
89+
}
90+
8791
for clause in elaborate::elaborate(
8892
cx,
8993
cx.explicit_implied_const_bounds(alias_ty.def_id)
@@ -338,10 +342,27 @@ where
338342
}
339343

340344
fn consider_builtin_destruct_candidate(
341-
_ecx: &mut EvalCtxt<'_, D>,
342-
_goal: Goal<I, Self>,
345+
ecx: &mut EvalCtxt<'_, D>,
346+
goal: Goal<I, Self>,
343347
) -> Result<Candidate<I>, NoSolution> {
344-
Err(NoSolution)
348+
let cx = ecx.cx();
349+
350+
let self_ty = goal.predicate.self_ty();
351+
let const_conditions = structural_traits::const_conditions_for_destruct(cx, self_ty)?;
352+
353+
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
354+
ecx.add_goals(
355+
GoalSource::Misc,
356+
const_conditions.into_iter().map(|trait_ref| {
357+
goal.with(
358+
cx,
359+
ty::Binder::dummy(trait_ref)
360+
.to_host_effect_clause(cx, goal.predicate.constness),
361+
)
362+
}),
363+
);
364+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
365+
})
345366
}
346367

347368
fn consider_builtin_transmute_candidate(

compiler/rustc_type_ir/src/inherent.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_ast_ir::Mutability;
1111
use crate::elaborate::Elaboratable;
1212
use crate::fold::{TypeFoldable, TypeSuperFoldable};
1313
use crate::relate::Relate;
14-
use crate::solve::Reveal;
14+
use crate::solve::{AdtDestructorKind, Reveal};
1515
use crate::visit::{Flags, TypeSuperVisitable, TypeVisitable};
1616
use crate::{self as ty, CollectAndApply, Interner, UpcastFrom};
1717

@@ -537,6 +537,8 @@ pub trait AdtDef<I: Interner>: Copy + Debug + Hash + Eq {
537537
fn sized_constraint(self, interner: I) -> Option<ty::EarlyBinder<I, I::Ty>>;
538538

539539
fn is_fundamental(self) -> bool;
540+
541+
fn destructor(self, interner: I) -> Option<AdtDestructorKind>;
540542
}
541543

542544
pub trait ParamEnv<I: Interner>: Copy + Debug + Hash + Eq + TypeFoldable<I> {

compiler/rustc_type_ir/src/interner.rs

+1
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ pub trait Interner:
225225

226226
fn impl_is_const(self, def_id: Self::DefId) -> bool;
227227
fn fn_is_const(self, def_id: Self::DefId) -> bool;
228+
fn alias_has_const_conditions(self, def_id: Self::DefId) -> bool;
228229
fn const_conditions(
229230
self,
230231
def_id: Self::DefId,

compiler/rustc_type_ir/src/lang_items.rs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub enum TraitSolverLangItem {
1919
CoroutineYield,
2020
Destruct,
2121
DiscriminantKind,
22+
Drop,
2223
DynMetadata,
2324
Fn,
2425
FnMut,

compiler/rustc_type_ir/src/solve/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -326,3 +326,10 @@ impl MaybeCause {
326326
}
327327
}
328328
}
329+
330+
/// Indicates that a `impl Drop for Adt` is `const` or not.
331+
#[derive(Debug)]
332+
pub enum AdtDestructorKind {
333+
NotConst,
334+
Const,
335+
}

0 commit comments

Comments
 (0)