Skip to content

Commit 34e0e82

Browse files
compiler-errorsjdonszelmann
authored andcommitted
Rework unsizing coercions in new solver
1 parent c8e1e8d commit 34e0e82

File tree

3 files changed

+177
-117
lines changed

3 files changed

+177
-117
lines changed

compiler/rustc_hir_typeck/src/coercion.rs

Lines changed: 175 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@
3535
//! // and are then unable to coerce `&7i32` to `&mut i32`.
3636
//! ```
3737
38-
use std::ops::Deref;
38+
use std::ops::{ControlFlow, Deref};
3939

4040
use rustc_errors::codes::*;
4141
use rustc_errors::{Applicability, Diag, struct_span_code_err};
42-
use rustc_hir as hir;
4342
use rustc_hir::attrs::InlineAttr;
4443
use rustc_hir::def_id::{DefId, LocalDefId};
44+
use rustc_hir::{self as hir, LangItem};
4545
use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer;
4646
use rustc_infer::infer::relate::RelateResult;
4747
use rustc_infer::infer::{DefineOpaqueTypes, InferOk, InferResult, RegionVariableOrigin};
@@ -56,6 +56,8 @@ use rustc_middle::ty::error::TypeError;
5656
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt};
5757
use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Span};
5858
use rustc_trait_selection::infer::InferCtxtExt as _;
59+
use rustc_trait_selection::solve::inspect::{self, InferCtxtProofTreeExt, ProofTreeVisitor};
60+
use rustc_trait_selection::solve::{Certainty, Goal, NoSolution};
5961
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
6062
use rustc_trait_selection::traits::{
6163
self, ImplSource, NormalizeExt, ObligationCause, ObligationCauseCode, ObligationCtxt,
@@ -639,131 +641,140 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
639641
Adjust::Pointer(PointerCoercion::Unsize),
640642
)?;
641643

642-
let mut selcx = traits::SelectionContext::new(self);
643-
644644
// Create an obligation for `Source: CoerceUnsized<Target>`.
645645
let cause = self.cause(self.cause.span, ObligationCauseCode::Coercion { source, target });
646+
let pred = ty::TraitRef::new(self.tcx, coerce_unsized_did, [coerce_source, coerce_target]);
647+
let obligation = Obligation::new(self.tcx, cause, self.fcx.param_env, pred);
646648

647-
// Use a FIFO queue for this custom fulfillment procedure.
648-
//
649-
// A Vec (or SmallVec) is not a natural choice for a queue. However,
650-
// this code path is hot, and this queue usually has a max length of 1
651-
// and almost never more than 3. By using a SmallVec we avoid an
652-
// allocation, at the (very small) cost of (occasionally) having to
653-
// shift subsequent elements down when removing the front element.
654-
let mut queue: SmallVec<[PredicateObligation<'tcx>; 4]> = smallvec![Obligation::new(
655-
self.tcx,
656-
cause,
657-
self.fcx.param_env,
658-
ty::TraitRef::new(self.tcx, coerce_unsized_did, [coerce_source, coerce_target])
659-
)];
660-
661-
// Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid
662-
// emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where
663-
// inference might unify those two inner type variables later.
664-
let traits = [coerce_unsized_did, unsize_did];
665-
while !queue.is_empty() {
666-
let obligation = queue.remove(0);
667-
let trait_pred = match obligation.predicate.kind().no_bound_vars() {
668-
Some(ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)))
669-
if traits.contains(&trait_pred.def_id()) =>
670-
{
671-
self.resolve_vars_if_possible(trait_pred)
672-
}
673-
// Eagerly process alias-relate obligations in new trait solver,
674-
// since these can be emitted in the process of solving trait goals,
675-
// but we need to constrain vars before processing goals mentioning
676-
// them.
677-
Some(ty::PredicateKind::AliasRelate(..)) => {
678-
let ocx = ObligationCtxt::new(self);
679-
ocx.register_obligation(obligation);
680-
if !ocx.try_evaluate_obligations().is_empty() {
681-
return Err(TypeError::Mismatch);
649+
if self.next_trait_solver() {
650+
coercion.obligations.push(obligation);
651+
652+
if self
653+
.infcx
654+
.visit_proof_tree(
655+
Goal::new(self.tcx, self.param_env, pred),
656+
&mut CoerceVisitor { fcx: self.fcx, span: self.cause.span },
657+
)
658+
.is_break()
659+
{
660+
return Err(TypeError::Mismatch);
661+
}
662+
} else {
663+
let mut selcx = traits::SelectionContext::new(self);
664+
// Use a FIFO queue for this custom fulfillment procedure.
665+
//
666+
// A Vec (or SmallVec) is not a natural choice for a queue. However,
667+
// this code path is hot, and this queue usually has a max length of 1
668+
// and almost never more than 3. By using a SmallVec we avoid an
669+
// allocation, at the (very small) cost of (occasionally) having to
670+
// shift subsequent elements down when removing the front element.
671+
let mut queue: SmallVec<[PredicateObligation<'tcx>; 4]> = smallvec![obligation];
672+
673+
// Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid
674+
// emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where
675+
// inference might unify those two inner type variables later.
676+
let traits = [coerce_unsized_did, unsize_did];
677+
while !queue.is_empty() {
678+
let obligation = queue.remove(0);
679+
let trait_pred = match obligation.predicate.kind().no_bound_vars() {
680+
Some(ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)))
681+
if traits.contains(&trait_pred.def_id()) =>
682+
{
683+
self.resolve_vars_if_possible(trait_pred)
682684
}
683-
coercion.obligations.extend(ocx.into_pending_obligations());
684-
continue;
685-
}
686-
_ => {
687-
coercion.obligations.push(obligation);
688-
continue;
689-
}
690-
};
691-
debug!("coerce_unsized resolve step: {:?}", trait_pred);
692-
match selcx.select(&obligation.with(selcx.tcx(), trait_pred)) {
693-
// Uncertain or unimplemented.
694-
Ok(None) => {
695-
if trait_pred.def_id() == unsize_did {
696-
let self_ty = trait_pred.self_ty();
697-
let unsize_ty = trait_pred.trait_ref.args[1].expect_ty();
698-
debug!("coerce_unsized: ambiguous unsize case for {:?}", trait_pred);
699-
match (self_ty.kind(), unsize_ty.kind()) {
700-
(&ty::Infer(ty::TyVar(v)), ty::Dynamic(..))
701-
if self.type_var_is_sized(v) =>
702-
{
703-
debug!("coerce_unsized: have sized infer {:?}", v);
704-
coercion.obligations.push(obligation);
705-
// `$0: Unsize<dyn Trait>` where we know that `$0: Sized`, try going
706-
// for unsizing.
707-
}
708-
_ => {
709-
// Some other case for `$0: Unsize<Something>`. Note that we
710-
// hit this case even if `Something` is a sized type, so just
711-
// don't do the coercion.
712-
debug!("coerce_unsized: ambiguous unsize");
713-
return Err(TypeError::Mismatch);
685+
// Eagerly process alias-relate obligations in new trait solver,
686+
// since these can be emitted in the process of solving trait goals,
687+
// but we need to constrain vars before processing goals mentioning
688+
// them.
689+
Some(ty::PredicateKind::AliasRelate(..)) => {
690+
let ocx = ObligationCtxt::new(self);
691+
ocx.register_obligation(obligation);
692+
if !ocx.try_evaluate_obligations().is_empty() {
693+
return Err(TypeError::Mismatch);
694+
}
695+
coercion.obligations.extend(ocx.into_pending_obligations());
696+
continue;
697+
}
698+
_ => {
699+
coercion.obligations.push(obligation);
700+
continue;
701+
}
702+
};
703+
debug!("coerce_unsized resolve step: {:?}", trait_pred);
704+
match selcx.select(&obligation.with(selcx.tcx(), trait_pred)) {
705+
// Uncertain or unimplemented.
706+
Ok(None) => {
707+
if trait_pred.def_id() == unsize_did {
708+
let self_ty = trait_pred.self_ty();
709+
let unsize_ty = trait_pred.trait_ref.args[1].expect_ty();
710+
debug!("coerce_unsized: ambiguous unsize case for {:?}", trait_pred);
711+
match (self_ty.kind(), unsize_ty.kind()) {
712+
(&ty::Infer(ty::TyVar(v)), ty::Dynamic(..))
713+
if self.type_var_is_sized(v) =>
714+
{
715+
debug!("coerce_unsized: have sized infer {:?}", v);
716+
coercion.obligations.push(obligation);
717+
// `$0: Unsize<dyn Trait>` where we know that `$0: Sized`, try going
718+
// for unsizing.
719+
}
720+
_ => {
721+
// Some other case for `$0: Unsize<Something>`. Note that we
722+
// hit this case even if `Something` is a sized type, so just
723+
// don't do the coercion.
724+
debug!("coerce_unsized: ambiguous unsize");
725+
return Err(TypeError::Mismatch);
726+
}
714727
}
728+
} else {
729+
debug!("coerce_unsized: early return - ambiguous");
730+
return Err(TypeError::Mismatch);
715731
}
716-
} else {
717-
debug!("coerce_unsized: early return - ambiguous");
732+
}
733+
Err(SelectionError::Unimplemented) => {
734+
debug!("coerce_unsized: early return - can't prove obligation");
718735
return Err(TypeError::Mismatch);
719736
}
720-
}
721-
Err(SelectionError::Unimplemented) => {
722-
debug!("coerce_unsized: early return - can't prove obligation");
723-
return Err(TypeError::Mismatch);
724-
}
725737

726-
Err(SelectionError::TraitDynIncompatible(_)) => {
727-
// Dyn compatibility errors in coercion will *always* be due to the
728-
// fact that the RHS of the coercion is a non-dyn compatible `dyn Trait`
729-
// written in source somewhere (otherwise we will never have lowered
730-
// the dyn trait from HIR to middle).
731-
//
732-
// There's no reason to emit yet another dyn compatibility error,
733-
// especially since the span will differ slightly and thus not be
734-
// deduplicated at all!
735-
self.fcx.set_tainted_by_errors(
736-
self.fcx
737-
.dcx()
738-
.span_delayed_bug(self.cause.span, "dyn compatibility during coercion"),
739-
);
740-
}
741-
Err(err) => {
742-
let guar = self.err_ctxt().report_selection_error(
743-
obligation.clone(),
744-
&obligation,
745-
&err,
746-
);
747-
self.fcx.set_tainted_by_errors(guar);
748-
// Treat this like an obligation and follow through
749-
// with the unsizing - the lack of a coercion should
750-
// be silent, as it causes a type mismatch later.
751-
}
752-
753-
Ok(Some(ImplSource::UserDefined(impl_source))) => {
754-
queue.extend(impl_source.nested);
755-
// Certain incoherent `CoerceUnsized` implementations may cause ICEs,
756-
// so check the impl's validity. Taint the body so that we don't try
757-
// to evaluate these invalid coercions in CTFE. We only need to do this
758-
// for local impls, since upstream impls should be valid.
759-
if impl_source.impl_def_id.is_local()
760-
&& let Err(guar) =
761-
self.tcx.ensure_ok().coerce_unsized_info(impl_source.impl_def_id)
762-
{
738+
Err(SelectionError::TraitDynIncompatible(_)) => {
739+
// Dyn compatibility errors in coercion will *always* be due to the
740+
// fact that the RHS of the coercion is a non-dyn compatible `dyn Trait`
741+
// writen in source somewhere (otherwise we will never have lowered
742+
// the dyn trait from HIR to middle).
743+
//
744+
// There's no reason to emit yet another dyn compatibility error,
745+
// especially since the span will differ slightly and thus not be
746+
// deduplicated at all!
747+
self.fcx.set_tainted_by_errors(self.fcx.dcx().span_delayed_bug(
748+
self.cause.span,
749+
"dyn compatibility during coercion",
750+
));
751+
}
752+
Err(err) => {
753+
let guar = self.err_ctxt().report_selection_error(
754+
obligation.clone(),
755+
&obligation,
756+
&err,
757+
);
763758
self.fcx.set_tainted_by_errors(guar);
759+
// Treat this like an obligation and follow through
760+
// with the unsizing - the lack of a coercion should
761+
// be silent, as it causes a type mismatch later.
762+
}
763+
Ok(Some(ImplSource::UserDefined(impl_source))) => {
764+
queue.extend(impl_source.nested);
765+
// Certain incoherent `CoerceUnsized` implementations may cause ICEs,
766+
// so check the impl's validity. Taint the body so that we don't try
767+
// to evaluate these invalid coercions in CTFE. We only need to do this
768+
// for local impls, since upstream impls should be valid.
769+
if impl_source.impl_def_id.is_local()
770+
&& let Err(guar) =
771+
self.tcx.ensure_ok().coerce_unsized_info(impl_source.impl_def_id)
772+
{
773+
self.fcx.set_tainted_by_errors(guar);
774+
}
764775
}
776+
Ok(Some(impl_source)) => queue.extend(impl_source.nested_obligations()),
765777
}
766-
Ok(Some(impl_source)) => queue.extend(impl_source.nested_obligations()),
767778
}
768779
}
769780

@@ -2005,3 +2016,52 @@ impl AsCoercionSite for hir::Arm<'_> {
20052016
self.body
20062017
}
20072018
}
2019+
2020+
/// Recursively visit goals to decide whether an unsizing is possible.
2021+
/// `Break`s when it isn't, and an error should be raised.
2022+
/// `Continue`s when an unsizing ok based on an implementation of the `Unsize` trait / lang item.
2023+
struct CoerceVisitor<'a, 'tcx> {
2024+
fcx: &'a FnCtxt<'a, 'tcx>,
2025+
span: Span,
2026+
}
2027+
2028+
impl<'tcx> ProofTreeVisitor<'tcx> for CoerceVisitor<'_, 'tcx> {
2029+
type Result = ControlFlow<()>;
2030+
2031+
fn span(&self) -> Span {
2032+
self.span
2033+
}
2034+
2035+
fn visit_goal(&mut self, goal: &inspect::InspectGoal<'_, 'tcx>) -> Self::Result {
2036+
let Some(pred) = goal.goal().predicate.as_trait_clause() else {
2037+
return ControlFlow::Continue(());
2038+
};
2039+
2040+
if !self.fcx.tcx.is_lang_item(pred.def_id(), LangItem::Unsize)
2041+
&& !self.fcx.tcx.is_lang_item(pred.def_id(), LangItem::CoerceUnsized)
2042+
{
2043+
return ControlFlow::Continue(());
2044+
}
2045+
2046+
match goal.result() {
2047+
Ok(Certainty::Yes) => ControlFlow::Continue(()),
2048+
Err(NoSolution) => ControlFlow::Break(()),
2049+
Ok(Certainty::Maybe { .. }) => {
2050+
// FIXME: structurally normalize?
2051+
if self.fcx.tcx.is_lang_item(pred.def_id(), LangItem::Unsize)
2052+
&& let ty::Dynamic(..) = pred.skip_binder().trait_ref.args.type_at(1).kind()
2053+
&& let ty::Infer(ty::TyVar(vid)) = *pred.self_ty().skip_binder().kind()
2054+
&& self.fcx.type_var_is_sized(vid)
2055+
{
2056+
ControlFlow::Continue(())
2057+
} else if let Some(cand) = goal.unique_applicable_candidate()
2058+
&& cand.shallow_certainty() == Certainty::Yes
2059+
{
2060+
cand.visit_nested_no_probe(self)
2061+
} else {
2062+
ControlFlow::Break(())
2063+
}
2064+
}
2065+
}
2066+
}
2067+
}

compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_middle::ty::{self, Ty, TypeVisitableExt};
66
use rustc_span::Span;
77
use rustc_trait_selection::solve::Certainty;
88
use rustc_trait_selection::solve::inspect::{
9-
InspectConfig, InspectGoal, InferCtxtProofTreeExt, ProofTreeVisitor,
9+
InferCtxtProofTreeExt, InspectConfig, InspectGoal, ProofTreeVisitor,
1010
};
1111
use tracing::{debug, instrument, trace};
1212

compiler/rustc_trait_selection/src/traits/coherence.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use tracing::{debug, instrument, warn};
2929
use super::ObligationCtxt;
3030
use crate::error_reporting::traits::suggest_new_overflow_limit;
3131
use crate::infer::InferOk;
32-
use crate::solve::inspect::{InspectGoal, InferCtxtProofTreeExt, ProofTreeVisitor};
32+
use crate::solve::inspect::{InferCtxtProofTreeExt, InspectGoal, ProofTreeVisitor};
3333
use crate::solve::{SolverDelegate, deeply_normalize_for_diagnostics, inspect};
3434
use crate::traits::query::evaluate_obligation::InferCtxtExt;
3535
use crate::traits::select::IntercrateAmbiguityCause;

0 commit comments

Comments
 (0)