Rollup merge of #132119 - compiler-errors:effects-old-solver, r=lcnr

Hack out effects support for old solver

Opening this for vibes 

Turns out that a basic, somewhat incomplete implementation of host effects is achievable in the old trait solver pretty easily. This should be sufficient for us to use in the standard library itself.

Regarding incompleteness, maybe we should always treat host predicates as ambiguous in intercrate mode (at least in the old solver) to avoid any worries about accidental impl overlap or something.

r? ```@lcnr``` cc ```@fee1-dead```
This commit is contained in:
Jubilee 2024-10-29 03:11:40 -07:00 committed by GitHub
commit e97286e738
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 302 additions and 192 deletions

View file

@ -149,10 +149,6 @@ hir_analysis_drop_impl_reservation = reservation `Drop` impls are not supported
hir_analysis_duplicate_precise_capture = cannot capture parameter `{$name}` twice
.label = parameter captured again here
hir_analysis_effects_without_next_solver = using `#![feature(effects)]` without enabling next trait solver globally
.note = the next trait solver must be enabled globally for the effects feature to work correctly
.help = use `-Znext-solver` to enable
hir_analysis_empty_specialization = specialization impl does not specialize any associated items
.note = impl is a specialization of this impl

View file

@ -1623,12 +1623,6 @@ pub(crate) struct InvalidReceiverTy<'tcx> {
pub receiver_ty: Ty<'tcx>,
}
#[derive(Diagnostic)]
#[diag(hir_analysis_effects_without_next_solver)]
#[note]
#[help]
pub(crate) struct EffectsWithoutNextSolver;
#[derive(Diagnostic)]
#[diag(hir_analysis_cmse_inputs_stack_spill, code = E0798)]
#[note]

View file

@ -153,12 +153,6 @@ pub fn provide(providers: &mut Providers) {
pub fn check_crate(tcx: TyCtxt<'_>) {
let _prof_timer = tcx.sess.timer("type_check_crate");
// FIXME(effects): remove once effects is implemented in old trait solver
// or if the next solver is stabilized.
if tcx.features().effects() && !tcx.next_trait_solver_globally() {
tcx.dcx().emit_err(errors::EffectsWithoutNextSolver);
}
tcx.sess.time("coherence_checking", || {
tcx.hir().par_for_each_module(|module| {
let _ = tcx.ensure().check_mod_type_wf(module);

View file

@ -0,0 +1,152 @@
use rustc_hir as hir;
use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes, InferCtxt};
use rustc_infer::traits::{ImplSource, Obligation, PredicateObligation};
use rustc_middle::ty::fast_reject::DeepRejectCtxt;
use rustc_middle::{span_bug, ty};
use rustc_type_ir::solve::NoSolution;
use thin_vec::ThinVec;
use super::SelectionContext;
pub type HostEffectObligation<'tcx> = Obligation<'tcx, ty::HostEffectPredicate<'tcx>>;
pub enum EvaluationFailure {
Ambiguous,
NoSolution,
}
pub fn evaluate_host_effect_obligation<'tcx>(
selcx: &mut SelectionContext<'_, 'tcx>,
obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
if selcx.infcx.intercrate {
span_bug!(
obligation.cause.span,
"should not select host obligation in old solver in intercrate mode"
);
}
match evaluate_host_effect_from_bounds(selcx, obligation) {
Ok(result) => return Ok(result),
Err(EvaluationFailure::Ambiguous) => return Err(EvaluationFailure::Ambiguous),
Err(EvaluationFailure::NoSolution) => {}
}
match evaluate_host_effect_from_selection_candiate(selcx, obligation) {
Ok(result) => return Ok(result),
Err(EvaluationFailure::Ambiguous) => return Err(EvaluationFailure::Ambiguous),
Err(EvaluationFailure::NoSolution) => {}
}
Err(EvaluationFailure::NoSolution)
}
fn match_candidate<'tcx>(
infcx: &InferCtxt<'tcx>,
obligation: &HostEffectObligation<'tcx>,
candidate: ty::Binder<'tcx, ty::HostEffectPredicate<'tcx>>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, NoSolution> {
if !candidate.skip_binder().host.satisfies(obligation.predicate.host) {
return Err(NoSolution);
}
let candidate = infcx.instantiate_binder_with_fresh_vars(
obligation.cause.span,
BoundRegionConversionTime::HigherRankedType,
candidate,
);
let mut nested = infcx
.at(&obligation.cause, obligation.param_env)
.eq(DefineOpaqueTypes::Yes, obligation.predicate.trait_ref, candidate.trait_ref)?
.into_obligations();
for nested in &mut nested {
nested.set_depth_from_parent(obligation.recursion_depth);
}
Ok(nested)
}
fn evaluate_host_effect_from_bounds<'tcx>(
selcx: &mut SelectionContext<'_, 'tcx>,
obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
let infcx = selcx.infcx;
let drcx = DeepRejectCtxt::relate_rigid_rigid(selcx.tcx());
let mut candidate = None;
for predicate in obligation.param_env.caller_bounds() {
let bound_predicate = predicate.kind();
if let ty::ClauseKind::HostEffect(data) = predicate.kind().skip_binder() {
let data = bound_predicate.rebind(data);
if data.skip_binder().trait_ref.def_id != obligation.predicate.trait_ref.def_id {
continue;
}
if !drcx.args_may_unify(
obligation.predicate.trait_ref.args,
data.skip_binder().trait_ref.args,
) {
continue;
}
let is_match = infcx.probe(|_| match_candidate(infcx, obligation, data).is_ok());
if is_match {
if candidate.is_some() {
return Err(EvaluationFailure::Ambiguous);
} else {
candidate = Some(data);
}
}
}
}
if let Some(data) = candidate {
Ok(match_candidate(infcx, obligation, data)
.expect("candidate matched before, so it should match again"))
} else {
Err(EvaluationFailure::NoSolution)
}
}
fn evaluate_host_effect_from_selection_candiate<'tcx>(
selcx: &mut SelectionContext<'_, 'tcx>,
obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
let tcx = selcx.tcx();
selcx.infcx.commit_if_ok(|_| {
match selcx.select(&obligation.with(tcx, obligation.predicate.trait_ref)) {
Ok(None) => Err(EvaluationFailure::Ambiguous),
Err(_) => Err(EvaluationFailure::NoSolution),
Ok(Some(source)) => match source {
ImplSource::UserDefined(impl_) => {
if tcx.constness(impl_.impl_def_id) != hir::Constness::Const {
return Err(EvaluationFailure::NoSolution);
}
let mut nested = impl_.nested;
nested.extend(
tcx.const_conditions(impl_.impl_def_id)
.instantiate(tcx, impl_.args)
.into_iter()
.map(|(trait_ref, _)| {
obligation.with(
tcx,
trait_ref.to_host_effect_clause(tcx, obligation.predicate.host),
)
}),
);
for nested in &mut nested {
nested.set_depth_from_parent(obligation.recursion_depth);
}
Ok(nested)
}
_ => Err(EvaluationFailure::NoSolution),
},
}
})
}

View file

@ -17,6 +17,7 @@ use rustc_middle::ty::{self, Binder, Const, GenericArgsRef, TypeVisitableExt};
use thin_vec::ThinVec;
use tracing::{debug, debug_span, instrument};
use super::effects::{self, HostEffectObligation};
use super::project::{self, ProjectAndUnifyResult};
use super::select::SelectionContext;
use super::{
@ -402,8 +403,13 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> {
)
}
ty::PredicateKind::Clause(ty::ClauseKind::HostEffect(..)) => {
ProcessResult::Changed(Default::default())
ty::PredicateKind::Clause(ty::ClauseKind::HostEffect(data)) => {
let host_obligation = obligation.with(infcx.tcx, data);
self.process_host_obligation(
host_obligation,
&mut pending_obligation.stalled_on,
)
}
ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(data)) => {
@ -854,6 +860,27 @@ impl<'a, 'tcx> FulfillProcessor<'a, 'tcx> {
}
}
}
fn process_host_obligation(
&mut self,
host_obligation: HostEffectObligation<'tcx>,
stalled_on: &mut Vec<TyOrConstInferVar>,
) -> ProcessResult<PendingPredicateObligation<'tcx>, FulfillmentErrorCode<'tcx>> {
match effects::evaluate_host_effect_obligation(&mut self.selcx, &host_obligation) {
Ok(nested) => ProcessResult::Changed(mk_pending(nested)),
Err(effects::EvaluationFailure::Ambiguous) => {
stalled_on.clear();
stalled_on.extend(args_infer_vars(
&self.selcx,
ty::Binder::dummy(host_obligation.predicate.trait_ref.args),
));
ProcessResult::Unchanged
}
Err(effects::EvaluationFailure::NoSolution) => {
ProcessResult::Error(FulfillmentErrorCode::Select(SelectionError::Unimplemented))
}
}
}
}
/// Returns the set of inference variables contained in `args`.

View file

@ -6,6 +6,7 @@ pub mod auto_trait;
pub(crate) mod coherence;
pub mod const_evaluatable;
mod dyn_compatibility;
pub mod effects;
mod engine;
mod fulfill;
pub mod misc;

View file

@ -49,7 +49,7 @@ use crate::infer::{InferCtxt, InferOk, TypeFreshener};
use crate::solve::InferCtxtSelectExt as _;
use crate::traits::normalize::{normalize_with_depth, normalize_with_depth_to};
use crate::traits::project::{ProjectAndUnifyResult, ProjectionCacheKeyExt};
use crate::traits::{ProjectionCacheKey, Unimplemented};
use crate::traits::{ProjectionCacheKey, Unimplemented, effects};
mod _match;
mod candidate_assembly;
@ -645,11 +645,19 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
self.evaluate_trait_predicate_recursively(previous_stack, obligation)
}
ty::PredicateKind::Clause(ty::ClauseKind::HostEffect(..)) => {
// FIXME(effects): It should be relatively straightforward to implement
// old trait solver support for `HostEffect` bounds; or at least basic
// support for them.
todo!()
ty::PredicateKind::Clause(ty::ClauseKind::HostEffect(data)) => {
self.infcx.enter_forall(bound_predicate.rebind(data), |data| {
match effects::evaluate_host_effect_obligation(
self,
&obligation.with(self.tcx(), data),
) {
Ok(nested) => {
self.evaluate_predicates_recursively(previous_stack, nested)
}
Err(effects::EvaluationFailure::Ambiguous) => Ok(EvaluatedToAmbig),
Err(effects::EvaluationFailure::NoSolution) => Ok(EvaluatedToErr),
}
})
}
ty::PredicateKind::Subtype(p) => {