Rework unsizing coercions in new solver

This commit is contained in:
Michael Goulet 2025-06-02 17:45:06 +00:00 committed by Jana Dönszelmann
parent 14a2770c0f
commit 56894773c8
3 changed files with 181 additions and 121 deletions

View file

@ -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<Target>`.
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<dyn Trait>` where we know that `$0: Sized`, try going
// for unsizing.
}
_ => {
// Some other case for `$0: Unsize<Something>`. 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<dyn Trait>` where we know that `$0: Sized`, try going
// for unsizing.
}
_ => {
// Some other case for `$0: Unsize<Something>`. 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(())
}
}
}
}
}

View file

@ -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};

View file

@ -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;