From 56894773c8f9a72e40e388a25bd6c495998cdcce Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 2 Jun 2025 17:45:06 +0000 Subject: [PATCH] Rework unsizing coercions in new solver --- compiler/rustc_hir_typeck/src/coercion.rs | 298 +++++++++++------- .../src/fn_ctxt/inspect_obligations.rs | 2 +- .../src/traits/coherence.rs | 2 +- 3 files changed, 181 insertions(+), 121 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 1e5fea1db9fc..7766075bade9 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -35,13 +35,13 @@ //! // and are then unable to coerce `&7i32` to `&mut i32`. //! ``` -use std::ops::Deref; +use std::ops::{ControlFlow, Deref}; use rustc_errors::codes::*; use rustc_errors::{Applicability, Diag, struct_span_code_err}; -use rustc_hir as hir; use rustc_hir::attrs::InlineAttr; use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::{self as hir, LangItem}; use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer; use rustc_infer::infer::relate::RelateResult; use rustc_infer::infer::{DefineOpaqueTypes, InferOk, InferResult, RegionVariableOrigin}; @@ -56,6 +56,8 @@ use rustc_middle::ty::error::TypeError; use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Span}; use rustc_trait_selection::infer::InferCtxtExt as _; +use rustc_trait_selection::solve::inspect::{self, InferCtxtProofTreeExt, ProofTreeVisitor}; +use rustc_trait_selection::solve::{Certainty, Goal, NoSolution}; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use rustc_trait_selection::traits::{ self, ImplSource, NormalizeExt, ObligationCause, ObligationCauseCode, ObligationCtxt, @@ -639,131 +641,140 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { Adjust::Pointer(PointerCoercion::Unsize), )?; - let mut selcx = traits::SelectionContext::new(self); - // Create an obligation for `Source: CoerceUnsized`. let cause = self.cause(self.cause.span, ObligationCauseCode::Coercion { source, target }); + let pred = ty::TraitRef::new(self.tcx, coerce_unsized_did, [coerce_source, coerce_target]); + let obligation = Obligation::new(self.tcx, cause, self.fcx.param_env, pred); - // Use a FIFO queue for this custom fulfillment procedure. - // - // A Vec (or SmallVec) is not a natural choice for a queue. However, - // this code path is hot, and this queue usually has a max length of 1 - // and almost never more than 3. By using a SmallVec we avoid an - // allocation, at the (very small) cost of (occasionally) having to - // shift subsequent elements down when removing the front element. - let mut queue: SmallVec<[PredicateObligation<'tcx>; 4]> = smallvec![Obligation::new( - self.tcx, - cause, - self.fcx.param_env, - ty::TraitRef::new(self.tcx, coerce_unsized_did, [coerce_source, coerce_target]) - )]; + if self.next_trait_solver() { + coercion.obligations.push(obligation); - // Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid - // emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where - // inference might unify those two inner type variables later. - let traits = [coerce_unsized_did, unsize_did]; - while !queue.is_empty() { - let obligation = queue.remove(0); - let trait_pred = match obligation.predicate.kind().no_bound_vars() { - Some(ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred))) - if traits.contains(&trait_pred.def_id()) => - { - self.resolve_vars_if_possible(trait_pred) - } - // Eagerly process alias-relate obligations in new trait solver, - // since these can be emitted in the process of solving trait goals, - // but we need to constrain vars before processing goals mentioning - // them. - Some(ty::PredicateKind::AliasRelate(..)) => { - let ocx = ObligationCtxt::new(self); - ocx.register_obligation(obligation); - if !ocx.try_evaluate_obligations().is_empty() { - return Err(TypeError::Mismatch); - } - coercion.obligations.extend(ocx.into_pending_obligations()); - continue; - } - _ => { - coercion.obligations.push(obligation); - continue; - } - }; - debug!("coerce_unsized resolve step: {:?}", trait_pred); - match selcx.select(&obligation.with(selcx.tcx(), trait_pred)) { - // Uncertain or unimplemented. - Ok(None) => { - if trait_pred.def_id() == unsize_did { - let self_ty = trait_pred.self_ty(); - let unsize_ty = trait_pred.trait_ref.args[1].expect_ty(); - debug!("coerce_unsized: ambiguous unsize case for {:?}", trait_pred); - match (self_ty.kind(), unsize_ty.kind()) { - (&ty::Infer(ty::TyVar(v)), ty::Dynamic(..)) - if self.type_var_is_sized(v) => - { - debug!("coerce_unsized: have sized infer {:?}", v); - coercion.obligations.push(obligation); - // `$0: Unsize` where we know that `$0: Sized`, try going - // for unsizing. - } - _ => { - // Some other case for `$0: Unsize`. Note that we - // hit this case even if `Something` is a sized type, so just - // don't do the coercion. - debug!("coerce_unsized: ambiguous unsize"); - return Err(TypeError::Mismatch); - } - } - } else { - debug!("coerce_unsized: early return - ambiguous"); - return Err(TypeError::Mismatch); - } - } - Err(SelectionError::Unimplemented) => { - debug!("coerce_unsized: early return - can't prove obligation"); - return Err(TypeError::Mismatch); - } + if self + .infcx + .visit_proof_tree( + Goal::new(self.tcx, self.param_env, pred), + &mut CoerceVisitor { fcx: self.fcx, span: self.cause.span }, + ) + .is_break() + { + return Err(TypeError::Mismatch); + } + } else { + let mut selcx = traits::SelectionContext::new(self); + // Use a FIFO queue for this custom fulfillment procedure. + // + // A Vec (or SmallVec) is not a natural choice for a queue. However, + // this code path is hot, and this queue usually has a max length of 1 + // and almost never more than 3. By using a SmallVec we avoid an + // allocation, at the (very small) cost of (occasionally) having to + // shift subsequent elements down when removing the front element. + let mut queue: SmallVec<[PredicateObligation<'tcx>; 4]> = smallvec![obligation]; - Err(SelectionError::TraitDynIncompatible(_)) => { - // Dyn compatibility errors in coercion will *always* be due to the - // fact that the RHS of the coercion is a non-dyn compatible `dyn Trait` - // written in source somewhere (otherwise we will never have lowered - // the dyn trait from HIR to middle). - // - // There's no reason to emit yet another dyn compatibility error, - // especially since the span will differ slightly and thus not be - // deduplicated at all! - self.fcx.set_tainted_by_errors( - self.fcx - .dcx() - .span_delayed_bug(self.cause.span, "dyn compatibility during coercion"), - ); - } - Err(err) => { - let guar = self.err_ctxt().report_selection_error( - obligation.clone(), - &obligation, - &err, - ); - self.fcx.set_tainted_by_errors(guar); - // Treat this like an obligation and follow through - // with the unsizing - the lack of a coercion should - // be silent, as it causes a type mismatch later. - } - - Ok(Some(ImplSource::UserDefined(impl_source))) => { - queue.extend(impl_source.nested); - // Certain incoherent `CoerceUnsized` implementations may cause ICEs, - // so check the impl's validity. Taint the body so that we don't try - // to evaluate these invalid coercions in CTFE. We only need to do this - // for local impls, since upstream impls should be valid. - if impl_source.impl_def_id.is_local() - && let Err(guar) = - self.tcx.ensure_ok().coerce_unsized_info(impl_source.impl_def_id) + // Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid + // emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where + // inference might unify those two inner type variables later. + let traits = [coerce_unsized_did, unsize_did]; + while !queue.is_empty() { + let obligation = queue.remove(0); + let trait_pred = match obligation.predicate.kind().no_bound_vars() { + Some(ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred))) + if traits.contains(&trait_pred.def_id()) => { - self.fcx.set_tainted_by_errors(guar); + self.resolve_vars_if_possible(trait_pred) } + // Eagerly process alias-relate obligations in new trait solver, + // since these can be emitted in the process of solving trait goals, + // but we need to constrain vars before processing goals mentioning + // them. + Some(ty::PredicateKind::AliasRelate(..)) => { + let ocx = ObligationCtxt::new(self); + ocx.register_obligation(obligation); + if !ocx.try_evaluate_obligations().is_empty() { + return Err(TypeError::Mismatch); + } + coercion.obligations.extend(ocx.into_pending_obligations()); + continue; + } + _ => { + coercion.obligations.push(obligation); + continue; + } + }; + debug!("coerce_unsized resolve step: {:?}", trait_pred); + match selcx.select(&obligation.with(selcx.tcx(), trait_pred)) { + // Uncertain or unimplemented. + Ok(None) => { + if trait_pred.def_id() == unsize_did { + let self_ty = trait_pred.self_ty(); + let unsize_ty = trait_pred.trait_ref.args[1].expect_ty(); + debug!("coerce_unsized: ambiguous unsize case for {:?}", trait_pred); + match (self_ty.kind(), unsize_ty.kind()) { + (&ty::Infer(ty::TyVar(v)), ty::Dynamic(..)) + if self.type_var_is_sized(v) => + { + debug!("coerce_unsized: have sized infer {:?}", v); + coercion.obligations.push(obligation); + // `$0: Unsize` where we know that `$0: Sized`, try going + // for unsizing. + } + _ => { + // Some other case for `$0: Unsize`. Note that we + // hit this case even if `Something` is a sized type, so just + // don't do the coercion. + debug!("coerce_unsized: ambiguous unsize"); + return Err(TypeError::Mismatch); + } + } + } else { + debug!("coerce_unsized: early return - ambiguous"); + return Err(TypeError::Mismatch); + } + } + Err(SelectionError::Unimplemented) => { + debug!("coerce_unsized: early return - can't prove obligation"); + return Err(TypeError::Mismatch); + } + + Err(SelectionError::TraitDynIncompatible(_)) => { + // Dyn compatibility errors in coercion will *always* be due to the + // fact that the RHS of the coercion is a non-dyn compatible `dyn Trait` + // writen in source somewhere (otherwise we will never have lowered + // the dyn trait from HIR to middle). + // + // There's no reason to emit yet another dyn compatibility error, + // especially since the span will differ slightly and thus not be + // deduplicated at all! + self.fcx.set_tainted_by_errors(self.fcx.dcx().span_delayed_bug( + self.cause.span, + "dyn compatibility during coercion", + )); + } + Err(err) => { + let guar = self.err_ctxt().report_selection_error( + obligation.clone(), + &obligation, + &err, + ); + self.fcx.set_tainted_by_errors(guar); + // Treat this like an obligation and follow through + // with the unsizing - the lack of a coercion should + // be silent, as it causes a type mismatch later. + } + Ok(Some(ImplSource::UserDefined(impl_source))) => { + queue.extend(impl_source.nested); + // Certain incoherent `CoerceUnsized` implementations may cause ICEs, + // so check the impl's validity. Taint the body so that we don't try + // to evaluate these invalid coercions in CTFE. We only need to do this + // for local impls, since upstream impls should be valid. + if impl_source.impl_def_id.is_local() + && let Err(guar) = + self.tcx.ensure_ok().coerce_unsized_info(impl_source.impl_def_id) + { + self.fcx.set_tainted_by_errors(guar); + } + } + Ok(Some(impl_source)) => queue.extend(impl_source.nested_obligations()), } - Ok(Some(impl_source)) => queue.extend(impl_source.nested_obligations()), } } @@ -2005,3 +2016,52 @@ impl AsCoercionSite for hir::Arm<'_> { self.body } } + +/// Recursively visit goals to decide whether an unsizing is possible. +/// `Break`s when it isn't, and an error should be raised. +/// `Continue`s when an unsizing ok based on an implementation of the `Unsize` trait / lang item. +struct CoerceVisitor<'a, 'tcx> { + fcx: &'a FnCtxt<'a, 'tcx>, + span: Span, +} + +impl<'tcx> ProofTreeVisitor<'tcx> for CoerceVisitor<'_, 'tcx> { + type Result = ControlFlow<()>; + + fn span(&self) -> Span { + self.span + } + + fn visit_goal(&mut self, goal: &inspect::InspectGoal<'_, 'tcx>) -> Self::Result { + let Some(pred) = goal.goal().predicate.as_trait_clause() else { + return ControlFlow::Continue(()); + }; + + if !self.fcx.tcx.is_lang_item(pred.def_id(), LangItem::Unsize) + && !self.fcx.tcx.is_lang_item(pred.def_id(), LangItem::CoerceUnsized) + { + return ControlFlow::Continue(()); + } + + match goal.result() { + Ok(Certainty::Yes) => ControlFlow::Continue(()), + Err(NoSolution) => ControlFlow::Break(()), + Ok(Certainty::Maybe { .. }) => { + // FIXME: structurally normalize? + if self.fcx.tcx.is_lang_item(pred.def_id(), LangItem::Unsize) + && let ty::Dynamic(..) = pred.skip_binder().trait_ref.args.type_at(1).kind() + && let ty::Infer(ty::TyVar(vid)) = *pred.self_ty().skip_binder().kind() + && self.fcx.type_var_is_sized(vid) + { + ControlFlow::Continue(()) + } else if let Some(cand) = goal.unique_applicable_candidate() + && cand.shallow_certainty() == Certainty::Yes + { + cand.visit_nested_no_probe(self) + } else { + ControlFlow::Break(()) + } + } + } + } +} diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs index 33f15409acf7..ff37cb75a5c5 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs @@ -6,7 +6,7 @@ use rustc_middle::ty::{self, Ty, TypeVisitableExt}; use rustc_span::Span; use rustc_trait_selection::solve::Certainty; use rustc_trait_selection::solve::inspect::{ - InspectConfig, InspectGoal, InferCtxtProofTreeExt, ProofTreeVisitor, + InferCtxtProofTreeExt, InspectConfig, InspectGoal, ProofTreeVisitor, }; use tracing::{debug, instrument, trace}; diff --git a/compiler/rustc_trait_selection/src/traits/coherence.rs b/compiler/rustc_trait_selection/src/traits/coherence.rs index 4c8648480bbd..1ccda94dc89b 100644 --- a/compiler/rustc_trait_selection/src/traits/coherence.rs +++ b/compiler/rustc_trait_selection/src/traits/coherence.rs @@ -29,7 +29,7 @@ use tracing::{debug, instrument, warn}; use super::ObligationCtxt; use crate::error_reporting::traits::suggest_new_overflow_limit; use crate::infer::InferOk; -use crate::solve::inspect::{InspectGoal, InferCtxtProofTreeExt, ProofTreeVisitor}; +use crate::solve::inspect::{InferCtxtProofTreeExt, InspectGoal, ProofTreeVisitor}; use crate::solve::{SolverDelegate, deeply_normalize_for_diagnostics, inspect}; use crate::traits::query::evaluate_obligation::InferCtxtExt; use crate::traits::select::IntercrateAmbiguityCause;