Skip to content

Commit

Permalink
Implement ~const Destruct in new solver
Browse files Browse the repository at this point in the history
  • Loading branch information
compiler-errors committed Oct 31, 2024
1 parent e39adf9 commit 242dd91
Show file tree
Hide file tree
Showing 14 changed files with 267 additions and 72 deletions.
81 changes: 42 additions & 39 deletions compiler/rustc_const_eval/src/check_consts/qualifs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
//!
//! See the `Qualif` trait for more info.
// FIXME(effects): This API should be really reworked. It's dangerously general for
// having basically only two use-cases that act in different ways.

use rustc_errors::ErrorGuaranteed;
use rustc_hir::LangItem;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::mir::*;
use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty};
use rustc_middle::ty::{self, AdtDef, Ty, TypingMode};
use rustc_middle::{bug, mir};
use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt};
use tracing::instrument;
Expand Down Expand Up @@ -59,19 +62,9 @@ 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 this `Qualif` is inherent to the given struct or enum.
///
/// By default, `Qualif`s propagate into ADTs in a structural way: An ADT only becomes
/// qualified if part of it is assigned a value with that `Qualif`. However, some ADTs *always*
/// have a certain `Qualif`, regardless of whether their fields have it. For example, a type
/// with a custom `Drop` impl is inherently `NeedsDrop`.
///
/// Returning `true` for `in_adt_inherently` but `false` for `in_any_value_of_ty` is unsound.
fn in_adt_inherently<'tcx>(
cx: &ConstCx<'_, 'tcx>,
adt: AdtDef<'tcx>,
args: GenericArgsRef<'tcx>,
) -> bool;
/// Returns `true` if the `Qualif` is not structural, i.e. that we should not recurse
/// into the operand.
fn is_non_structural<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool;

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

// Avoid selecting for `UnsafeCell` either.
if ty.ty_adt_def().is_some_and(|adt| adt.is_unsafe_cell()) {
return true;
}

// We do not use `ty.is_freeze` here, because that requires revealing opaque types, which
// requires borrowck, which in turn will invoke mir_const_qualifs again, causing a cycle error.
// Instead we invoke an obligation context manually, and provide the opaque type inference settings
Expand All @@ -125,11 +123,7 @@ impl Qualif for HasMutInterior {
!errors.is_empty()
}

fn in_adt_inherently<'tcx>(
_cx: &ConstCx<'_, 'tcx>,
adt: AdtDef<'tcx>,
_: GenericArgsRef<'tcx>,
) -> bool {
fn is_non_structural<'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()
Expand All @@ -140,6 +134,7 @@ impl Qualif for HasMutInterior {
}
}

// FIXME(effects): Get rid of this!
/// Constant containing an ADT that implements `Drop`.
/// This must be ruled out because implicit promotion would remove side-effects
/// that occur as part of dropping that value. N.B., the implicit promotion has
Expand All @@ -159,11 +154,7 @@ impl Qualif for NeedsDrop {
ty.needs_drop(cx.tcx, cx.param_env)
}

fn in_adt_inherently<'tcx>(
cx: &ConstCx<'_, 'tcx>,
adt: AdtDef<'tcx>,
_: GenericArgsRef<'tcx>,
) -> bool {
fn is_non_structural<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
adt.has_dtor(cx.tcx)
}

Expand Down Expand Up @@ -192,16 +183,32 @@ impl Qualif for NeedsNonConstDrop {
return false;
}

// FIXME(effects): Reimplement const drop checking.
NeedsDrop::in_any_value_of_ty(cx, ty)
if cx.tcx.features().effects() {
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)
}
}

fn in_adt_inherently<'tcx>(
cx: &ConstCx<'_, 'tcx>,
adt: AdtDef<'tcx>,
_: GenericArgsRef<'tcx>,
) -> bool {
adt.has_non_const_dtor(cx.tcx)
fn is_non_structural<'tcx>(cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>) -> bool {
// Even a `const` dtor may have `~const` bounds that may need to
// be satisfied, so this becomes non-structural as soon as the
// ADT gets a destructor at all.
adt.has_dtor(cx.tcx)
}

fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool {
Expand Down Expand Up @@ -257,14 +264,10 @@ where
Rvalue::Aggregate(kind, operands) => {
// Return early if we know that the struct or enum being constructed is always
// qualified.
if let AggregateKind::Adt(adt_did, _, args, ..) = **kind {
if let AggregateKind::Adt(adt_did, ..) = **kind {
let def = cx.tcx.adt_def(adt_did);
if Q::in_adt_inherently(cx, def, args) {
return true;
}
// Don't do any value-based reasoning for unions.
if def.is_union() && Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx)) {
return true;
if def.is_union() || Q::is_non_structural(cx, def) {
return Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx));
}
}

Expand Down
12 changes: 8 additions & 4 deletions compiler/rustc_middle/src/ty/adt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use rustc_query_system::ich::StableHashingContext;
use rustc_session::DataTypeKind;
use rustc_span::symbol::sym;
use rustc_target::abi::{FIRST_VARIANT, ReprOptions, VariantIdx};
use rustc_type_ir::solve::AdtDestructorKind;
use tracing::{debug, info, trace};

use super::{
Expand Down Expand Up @@ -232,6 +233,13 @@ impl<'tcx> rustc_type_ir::inherent::AdtDef<TyCtxt<'tcx>> for AdtDef<'tcx> {
fn is_fundamental(self) -> bool {
self.is_fundamental()
}

fn destructor(self, tcx: TyCtxt<'tcx>) -> Option<AdtDestructorKind> {
Some(match self.destructor(tcx)?.constness {
hir::Constness::Const => AdtDestructorKind::Const,
hir::Constness::NotConst => AdtDestructorKind::NotConst,
})
}
}

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

pub fn has_non_const_dtor(self, tcx: TyCtxt<'tcx>) -> bool {
matches!(self.destructor(tcx), Some(Destructor { constness: hir::Constness::NotConst, .. }))
}

/// Asserts this is a struct or union and returns its unique variant.
pub fn non_enum_variant(self) -> &'tcx VariantDef {
assert!(self.is_struct() || self.is_union());
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
self.is_conditionally_const(def_id)
}

fn alias_has_const_conditions(self, def_id: DefId) -> bool {
self.is_conditionally_const(def_id)
}

fn const_conditions(
self,
def_id: DefId,
Expand Down Expand Up @@ -667,6 +671,7 @@ bidirectional_lang_item_map! {
CoroutineYield,
Destruct,
DiscriminantKind,
Drop,
DynMetadata,
Fn,
FnMut,
Expand Down
96 changes: 91 additions & 5 deletions compiler/rustc_next_trait_solver/src/solve/effect_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use tracing::instrument;
use super::assembly::Candidate;
use crate::delegate::SolverDelegate;
use crate::solve::{
BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, NoSolution,
QueryResult, assembly,
AdtDestructorKind, BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource,
NoSolution, QueryResult, assembly,
};

impl<D, I> assembly::GoalKind<D> for ty::HostEffectPredicate<I>
Expand Down Expand Up @@ -84,6 +84,10 @@ where
let cx = ecx.cx();
let mut candidates = vec![];

if !ecx.cx().alias_has_const_conditions(alias_ty.def_id) {
return vec![];
}

// FIXME(effects): We elaborate here because the implied const bounds
// aren't necessarily elaborated. We probably should prefix this query
// with `explicit_`...
Expand Down Expand Up @@ -404,10 +408,92 @@ where
}

fn consider_builtin_destruct_candidate(
_ecx: &mut EvalCtxt<'_, D>,
_goal: Goal<I, Self>,
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
) -> Result<Candidate<I>, NoSolution> {
Err(NoSolution)
let cx = ecx.cx();

let self_ty = goal.predicate.self_ty();

let const_conditions = match self_ty.kind() {
// An ADT is `~const Destruct` only if all of the fields are,
// *and* if there is a `Drop` impl, that `Drop` impl is also `~const`.
ty::Adt(adt_def, args) => {
let mut const_conditions: Vec<_> = adt_def
.all_field_tys(cx)
.iter_instantiated(cx, args)
.map(|ty| goal.predicate.trait_ref.with_self_ty(cx, ty))
.collect();
match adt_def.destructor(cx) {
// `Drop` impl exists, but it's not const. Type cannot be `~const Destruct`.
Some(AdtDestructorKind::NotConst) => return Err(NoSolution),
// `Drop` impl exists, and it's const. Require `Ty: ~const Drop` to hold.
Some(AdtDestructorKind::Const) => {
let drop_def_id = cx.require_lang_item(TraitSolverLangItem::Drop);
let drop_trait_ref = ty::TraitRef::new(cx, drop_def_id, [self_ty]);
const_conditions.push(drop_trait_ref);
}
// No `Drop` impl, no need to require anything else.
None => {}
}
const_conditions
}

ty::Array(ty, _) | ty::Pat(ty, _) | ty::Slice(ty) => {
vec![goal.predicate.trait_ref.with_self_ty(cx, ty)]
}

ty::Tuple(tys) => {
tys.iter().map(|ty| goal.predicate.trait_ref.with_self_ty(cx, ty)).collect()
}

// Trivially implement `~const Destruct`
ty::Bool
| ty::Char
| ty::Int(..)
| ty::Uint(..)
| ty::Float(..)
| ty::Str
| ty::RawPtr(..)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::Never
| ty::Infer(ty::InferTy::FloatVar(_) | ty::InferTy::IntVar(_))
| ty::Error(_) => vec![],

// Coroutines and closures could implement `~const Drop`,
// but they don't really need to right now.
ty::Closure(_, _)
| ty::CoroutineClosure(_, _)
| ty::Coroutine(_, _)
| ty::CoroutineWitness(_, _) => return Err(NoSolution),

ty::Dynamic(..)
| ty::Param(_)
| ty::Alias(..)
| ty::Placeholder(_)
| ty::Foreign(_) => return Err(NoSolution),

ty::Bound(..)
| ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
panic!("unexpected type `{self_ty:?}`")
}
};

ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
ecx.add_goals(
GoalSource::Misc,
const_conditions.into_iter().map(|trait_ref| {
goal.with(
cx,
ty::Binder::dummy(trait_ref)
.to_host_effect_clause(cx, goal.predicate.constness),
)
}),
);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}

fn consider_builtin_transmute_candidate(
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_type_ir/src/inherent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use rustc_ast_ir::Mutability;
use crate::elaborate::Elaboratable;
use crate::fold::{TypeFoldable, TypeSuperFoldable};
use crate::relate::Relate;
use crate::solve::Reveal;
use crate::solve::{AdtDestructorKind, Reveal};
use crate::visit::{Flags, TypeSuperVisitable, TypeVisitable};
use crate::{self as ty, CollectAndApply, Interner, UpcastFrom};

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

fn is_fundamental(self) -> bool;

fn destructor(self, interner: I) -> Option<AdtDestructorKind>;
}

pub trait ParamEnv<I: Interner>: Copy + Debug + Hash + Eq + TypeFoldable<I> {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_type_ir/src/interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ pub trait Interner:

fn impl_is_const(self, def_id: Self::DefId) -> bool;
fn fn_is_const(self, def_id: Self::DefId) -> bool;
fn alias_has_const_conditions(self, def_id: Self::DefId) -> bool;
fn const_conditions(
self,
def_id: Self::DefId,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_type_ir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub enum TraitSolverLangItem {
CoroutineYield,
Destruct,
DiscriminantKind,
Drop,
DynMetadata,
Fn,
FnMut,
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_type_ir/src/solve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,10 @@ impl MaybeCause {
}
}
}

/// Indicates that a `impl Drop for Adt` is `const` or not.
#[derive(Debug)]
pub enum AdtDestructorKind {
NotConst,
Const,
}
25 changes: 7 additions & 18 deletions tests/ui/consts/promoted_const_call.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,13 @@ LL | impl const Drop for Panic { fn drop(&mut self) { panic!(); } }
= note: marking a trait with `#[const_trait]` ensures all default method bodies are `const`
= note: adding a non-const method body in the future would be a breaking change

error[E0716]: temporary value dropped while borrowed
--> $DIR/promoted_const_call.rs:10:26
|
LL | let _: &'static _ = &id(&Panic);
| ---------- ^^^^^^^^^^ creates a temporary value which is freed while still in use
| |
| type annotation requires that borrow lasts for `'static`
...
LL | };
| - temporary value is freed at the end of this statement

error[E0716]: temporary value dropped while borrowed
error[E0493]: destructor of `Panic` cannot be evaluated at compile-time
--> $DIR/promoted_const_call.rs:10:30
|
LL | let _: &'static _ = &id(&Panic);
| ---------- ^^^^^ - temporary value is freed at the end of this statement
| | |
| | creates a temporary value which is freed while still in use
| type annotation requires that borrow lasts for `'static`
| ^^^^^ - value is dropped here
| |
| the destructor for this type cannot be evaluated in constants

error[E0716]: temporary value dropped while borrowed
--> $DIR/promoted_const_call.rs:16:26
Expand Down Expand Up @@ -69,6 +57,7 @@ LL | let _: &'static _ = &&(Panic, 0).1;
LL | }
| - temporary value is freed at the end of this statement

error: aborting due to 7 previous errors
error: aborting due to 6 previous errors

For more information about this error, try `rustc --explain E0716`.
Some errors have detailed explanations: E0493, E0716.
For more information about an error, try `rustc --explain E0493`.
Loading

0 comments on commit 242dd91

Please sign in to comment.