Skip to content

Commit 527788b

Browse files
committed
fix: Use ProofTreeVisitor for unsized coercion
1 parent fa3dbe6 commit 527788b

5 files changed

Lines changed: 156 additions & 148 deletions

File tree

crates/hir-ty/src/infer/coerce.rs

Lines changed: 115 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,23 @@
3535
//! // and are then unable to coerce `&7i32` to `&mut i32`.
3636
//! ```
3737
38+
use std::ops::ControlFlow;
39+
3840
use hir_def::{
39-
CallableDefId,
41+
CallableDefId, TraitId,
4042
attrs::AttrFlags,
4143
hir::{ExprId, ExprOrPatId},
4244
signatures::FunctionSignature,
4345
};
4446
use rustc_ast_ir::Mutability;
4547
use rustc_type_ir::{
46-
BoundVar, DebruijnIndex, TyVid, TypeAndMut, TypeFoldable, TypeFolder, TypeSuperFoldable,
47-
TypeVisitableExt,
48+
BoundVar, DebruijnIndex, InferTy, TyVid, TypeAndMut, TypeFoldable, TypeFolder,
49+
TypeSuperFoldable, TypeVisitableExt,
4850
error::TypeError,
49-
inherent::{
50-
Const as _, GenericArg as _, GenericArgs as _, IntoKind, Safety as _, SliceLike, Ty as _,
51-
},
51+
inherent::{Const as _, GenericArg as _, GenericArgs as _, IntoKind, Safety as _, Ty as _},
52+
solve::{Certainty, NoSolution},
5253
};
53-
use smallvec::{SmallVec, smallvec};
54+
use smallvec::SmallVec;
5455
use tracing::{debug, instrument};
5556

5657
use crate::{
@@ -62,16 +63,16 @@ use crate::{
6263
},
6364
next_solver::{
6465
Binder, BoundConst, BoundRegion, BoundRegionKind, BoundTy, BoundTyKind, CallableIdWrapper,
65-
Canonical, ClauseKind, CoercePredicate, Const, ConstKind, DbInterner, ErrorGuaranteed,
66-
GenericArgs, ParamEnv, PolyFnSig, PredicateKind, Region, RegionKind, TraitRef, Ty, TyKind,
66+
Canonical, CoercePredicate, Const, ConstKind, DbInterner, ErrorGuaranteed, GenericArgs,
67+
Goal, ParamEnv, PolyFnSig, PredicateKind, Region, RegionKind, TraitRef, Ty, TyKind,
6768
TypingMode,
6869
abi::Safety,
6970
infer::{
7071
DbInternerInferExt, InferCtxt, InferOk, InferResult,
7172
relate::RelateResult,
72-
select::{ImplSource, SelectionError},
73-
traits::{Obligation, ObligationCause, PredicateObligation, PredicateObligations},
73+
traits::{Obligation, ObligationCause, PredicateObligations},
7474
},
75+
inspect::{InspectGoal, ProofTreeVisitor},
7576
obligation_ctxt::ObligationCtxt,
7677
},
7778
upvars::upvars_mentioned,
@@ -85,9 +86,7 @@ trait CoerceDelegate<'db> {
8586

8687
fn set_diverging(&mut self, diverging_ty: Ty<'db>);
8788

88-
fn set_tainted_by_errors(&mut self);
89-
90-
fn type_var_is_sized(&mut self, var: TyVid) -> bool;
89+
fn type_var_is_sized(&self, var: TyVid) -> bool;
9190
}
9291

9392
struct Coerce<D> {
@@ -129,11 +128,6 @@ impl<'db, D> Coerce<D>
129128
where
130129
D: CoerceDelegate<'db>,
131130
{
132-
#[inline]
133-
fn set_tainted_by_errors(&mut self) {
134-
self.delegate.set_tainted_by_errors();
135-
}
136-
137131
#[inline]
138132
fn infcx(&self) -> &InferCtxt<'db> {
139133
self.delegate.infcx()
@@ -680,125 +674,30 @@ where
680674

681675
// Create an obligation for `Source: CoerceUnsized<Target>`.
682676
let cause = self.cause.clone();
683-
684-
// Use a FIFO queue for this custom fulfillment procedure.
685-
//
686-
// A Vec (or SmallVec) is not a natural choice for a queue. However,
687-
// this code path is hot, and this queue usually has a max length of 1
688-
// and almost never more than 3. By using a SmallVec we avoid an
689-
// allocation, at the (very small) cost of (occasionally) having to
690-
// shift subsequent elements down when removing the front element.
691-
let mut queue: SmallVec<[PredicateObligation<'db>; 4]> = smallvec![Obligation::new(
677+
let pred = TraitRef::new(
692678
self.interner(),
693-
cause,
694-
self.param_env(),
695-
TraitRef::new(
696-
self.interner(),
697-
coerce_unsized_did.into(),
698-
[coerce_source, coerce_target]
679+
coerce_unsized_did.into(),
680+
[coerce_source, coerce_target],
681+
);
682+
let obligation = Obligation::new(self.interner(), cause, self.param_env(), pred);
683+
684+
coercion.obligations.push(obligation);
685+
686+
if self
687+
.delegate
688+
.infcx()
689+
.visit_proof_tree(
690+
Goal::new(self.infcx().interner, self.param_env(), pred),
691+
&mut CoerceVisitor {
692+
delegate: &self.delegate,
693+
errored: false,
694+
unsize_did,
695+
coerce_unsized_did,
696+
},
699697
)
700-
)];
701-
// Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid
702-
// emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where
703-
// inference might unify those two inner type variables later.
704-
let traits = [coerce_unsized_did, unsize_did];
705-
while !queue.is_empty() {
706-
let obligation = queue.remove(0);
707-
let trait_pred = match obligation.predicate.kind().no_bound_vars() {
708-
Some(PredicateKind::Clause(ClauseKind::Trait(trait_pred)))
709-
if traits.contains(&trait_pred.def_id().0) =>
710-
{
711-
self.infcx().resolve_vars_if_possible(trait_pred)
712-
}
713-
// Eagerly process alias-relate obligations in new trait solver,
714-
// since these can be emitted in the process of solving trait goals,
715-
// but we need to constrain vars before processing goals mentioning
716-
// them.
717-
Some(PredicateKind::AliasRelate(..)) => {
718-
let mut ocx = ObligationCtxt::new(self.infcx());
719-
ocx.register_obligation(obligation);
720-
if !ocx.try_evaluate_obligations().is_empty() {
721-
return Err(TypeError::Mismatch);
722-
}
723-
coercion.obligations.extend(ocx.into_pending_obligations());
724-
continue;
725-
}
726-
_ => {
727-
coercion.obligations.push(obligation);
728-
continue;
729-
}
730-
};
731-
debug!("coerce_unsized resolve step: {:?}", trait_pred);
732-
match self.infcx().select(&obligation.with(self.interner(), trait_pred)) {
733-
// Uncertain or unimplemented.
734-
Ok(None) => {
735-
if trait_pred.def_id().0 == unsize_did {
736-
let self_ty = trait_pred.self_ty();
737-
let unsize_ty = trait_pred.trait_ref.args[1].expect_ty();
738-
debug!("coerce_unsized: ambiguous unsize case for {:?}", trait_pred);
739-
match (self_ty.kind(), unsize_ty.kind()) {
740-
(TyKind::Infer(rustc_type_ir::TyVar(v)), TyKind::Dynamic(..))
741-
if self.delegate.type_var_is_sized(v) =>
742-
{
743-
debug!("coerce_unsized: have sized infer {:?}", v);
744-
coercion.obligations.push(obligation);
745-
// `$0: Unsize<dyn Trait>` where we know that `$0: Sized`, try going
746-
// for unsizing.
747-
}
748-
_ => {
749-
// Some other case for `$0: Unsize<Something>`. Note that we
750-
// hit this case even if `Something` is a sized type, so just
751-
// don't do the coercion.
752-
debug!("coerce_unsized: ambiguous unsize");
753-
return Err(TypeError::Mismatch);
754-
}
755-
}
756-
} else {
757-
debug!("coerce_unsized: early return - ambiguous");
758-
if !coerce_source.references_non_lt_error()
759-
&& !coerce_target.references_non_lt_error()
760-
{
761-
// rustc always early-returns here, even when the types contains errors. However not bailing
762-
// improves error recovery, and while we don't implement generic consts properly, it also helps
763-
// correct code.
764-
return Err(TypeError::Mismatch);
765-
}
766-
}
767-
}
768-
Err(SelectionError::Unimplemented) => {
769-
debug!("coerce_unsized: early return - can't prove obligation");
770-
return Err(TypeError::Mismatch);
771-
}
772-
773-
Err(SelectionError::TraitDynIncompatible(_)) => {
774-
// Dyn compatibility errors in coercion will *always* be due to the
775-
// fact that the RHS of the coercion is a non-dyn compatible `dyn Trait`
776-
// written in source somewhere (otherwise we will never have lowered
777-
// the dyn trait from HIR to middle).
778-
//
779-
// There's no reason to emit yet another dyn compatibility error,
780-
// especially since the span will differ slightly and thus not be
781-
// deduplicated at all!
782-
self.set_tainted_by_errors();
783-
}
784-
Err(_err) => {
785-
// FIXME: Report an error:
786-
// let guar = self.err_ctxt().report_selection_error(
787-
// obligation.clone(),
788-
// &obligation,
789-
// &err,
790-
// );
791-
self.set_tainted_by_errors();
792-
// Treat this like an obligation and follow through
793-
// with the unsizing - the lack of a coercion should
794-
// be silent, as it causes a type mismatch later.
795-
}
796-
797-
Ok(Some(ImplSource::UserDefined(impl_source))) => {
798-
queue.extend(impl_source.nested);
799-
}
800-
Ok(Some(impl_source)) => queue.extend(impl_source.nested_obligations()),
801-
}
698+
.is_break()
699+
{
700+
return Err(TypeError::Mismatch);
802701
}
803702

804703
Ok(coercion)
@@ -983,12 +882,7 @@ impl<'db> CoerceDelegate<'db> for InferenceCoercionDelegate<'_, '_, 'db> {
983882
}
984883

985884
#[inline]
986-
fn set_tainted_by_errors(&mut self) {
987-
self.0.set_tainted_by_errors();
988-
}
989-
990-
#[inline]
991-
fn type_var_is_sized(&mut self, var: TyVid) -> bool {
885+
fn type_var_is_sized(&self, var: TyVid) -> bool {
992886
self.0.table.type_var_is_sized(var)
993887
}
994888
}
@@ -1560,8 +1454,7 @@ impl<'db> CoerceDelegate<'db> for HirCoercionDelegate<'_, 'db> {
15601454
(self.target_features, TargetFeatureIsSafeInTarget::No)
15611455
}
15621456
fn set_diverging(&mut self, _diverging_ty: Ty<'db>) {}
1563-
fn set_tainted_by_errors(&mut self) {}
1564-
fn type_var_is_sized(&mut self, _var: TyVid) -> bool {
1457+
fn type_var_is_sized(&self, _var: TyVid) -> bool {
15651458
false
15661459
}
15671460
}
@@ -1721,3 +1614,81 @@ fn is_capturing_closure(db: &dyn HirDatabase, closure: InternedClosureId) -> boo
17211614
upvars_mentioned(db, owner)
17221615
.is_some_and(|upvars| upvars.get(&expr).is_some_and(|upvars| !upvars.is_empty()))
17231616
}
1617+
1618+
/// Recursively visit goals to decide whether an unsizing is possible.
1619+
/// `Break`s when it isn't, and an error should be raised.
1620+
/// `Continue`s when an unsizing ok based on an implementation of the `Unsize` trait / lang item.
1621+
struct CoerceVisitor<'a, D> {
1622+
delegate: &'a D,
1623+
/// Whether the coercion is impossible. If so we sometimes still try to
1624+
/// coerce in these cases to emit better errors. This changes the behavior
1625+
/// when hitting the recursion limit.
1626+
errored: bool,
1627+
unsize_did: TraitId,
1628+
coerce_unsized_did: TraitId,
1629+
}
1630+
1631+
impl<'a, 'db, D: CoerceDelegate<'db>> ProofTreeVisitor<'db> for CoerceVisitor<'a, D> {
1632+
type Result = ControlFlow<()>;
1633+
1634+
fn visit_goal(&mut self, goal: &InspectGoal<'_, 'db>) -> Self::Result {
1635+
let Some(pred) = goal.goal().predicate.as_trait_clause() else {
1636+
return ControlFlow::Continue(());
1637+
};
1638+
1639+
// Make sure this predicate is referring to either an `Unsize` or `CoerceUnsized` trait,
1640+
// Otherwise there's nothing to do.
1641+
let def_id = pred.def_id().0;
1642+
if def_id != self.unsize_did && def_id != self.coerce_unsized_did {
1643+
return ControlFlow::Continue(());
1644+
}
1645+
1646+
match goal.result() {
1647+
// If we prove the `Unsize` or `CoerceUnsized` goal, continue recursing.
1648+
Ok(Certainty::Yes) => ControlFlow::Continue(()),
1649+
Err(NoSolution) => {
1650+
self.errored = true;
1651+
// Even if we find no solution, continue recursing if we find a single candidate
1652+
// for which we're shallowly certain it holds to get the right error source.
1653+
if let [only_candidate] = &goal.candidates()[..]
1654+
&& only_candidate.shallow_certainty() == Certainty::Yes
1655+
{
1656+
only_candidate.visit_nested_no_probe(self)
1657+
} else {
1658+
ControlFlow::Break(())
1659+
}
1660+
}
1661+
Ok(Certainty::Maybe { .. }) => {
1662+
// FIXME: structurally normalize?
1663+
if def_id == self.unsize_did
1664+
&& let TyKind::Dynamic(..) = pred.skip_binder().trait_ref.args.type_at(1).kind()
1665+
&& let TyKind::Infer(InferTy::TyVar(vid)) = pred.self_ty().skip_binder().kind()
1666+
&& self.delegate.type_var_is_sized(vid)
1667+
{
1668+
// We get here when trying to unsize a type variable to a `dyn Trait`,
1669+
// knowing that that variable is sized. Unsizing definitely has to happen in that case.
1670+
// If the variable weren't sized, we may not need an unsizing coercion.
1671+
// In general, we don't want to add coercions too eagerly since it makes error messages much worse.
1672+
ControlFlow::Continue(())
1673+
} else if let Some(cand) = goal.unique_applicable_candidate()
1674+
&& cand.shallow_certainty() == Certainty::Yes
1675+
{
1676+
cand.visit_nested_no_probe(self)
1677+
} else {
1678+
ControlFlow::Break(())
1679+
}
1680+
}
1681+
}
1682+
}
1683+
1684+
fn on_recursion_limit(&mut self) -> Self::Result {
1685+
if self.errored {
1686+
// This prevents accidentally committing unfulfilled unsized coercions while trying to
1687+
// find the error source for diagnostics.
1688+
// See https://github.com/rust-lang/trait-system-refactor-initiative/issues/266.
1689+
ControlFlow::Break(())
1690+
} else {
1691+
ControlFlow::Continue(())
1692+
}
1693+
}
1694+
}

crates/hir-ty/src/next_solver/inspect.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,9 +458,10 @@ impl<'a, 'db> InspectGoal<'a, 'db> {
458458
pub(crate) fn visit_with<V: ProofTreeVisitor<'db>>(&self, visitor: &mut V) -> V::Result {
459459
if self.depth < visitor.config().max_depth {
460460
try_visit!(visitor.visit_goal(self));
461+
V::Result::output()
462+
} else {
463+
visitor.on_recursion_limit()
461464
}
462-
463-
V::Result::output()
464465
}
465466
}
466467

@@ -473,6 +474,10 @@ pub(crate) trait ProofTreeVisitor<'db> {
473474
}
474475

475476
fn visit_goal(&mut self, goal: &InspectGoal<'_, 'db>) -> Self::Result;
477+
478+
fn on_recursion_limit(&mut self) -> Self::Result {
479+
Self::Result::output()
480+
}
476481
}
477482

478483
impl<'db> InferCtxt<'db> {

crates/hir-ty/src/tests/coercion.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,10 @@ fn test() {
598598
);
599599
}
600600

601+
// FIXME: rustc emits the following error here:
602+
// - error[E0277]: he size for values of type `impl Foo + ?Sized` cannot be known at compilation time
603+
// ...but we don't emit any error here for now
604+
#[ignore = "rustc emits E0277 here"]
601605
#[test]
602606
fn coerce_unsize_apit() {
603607
check(

0 commit comments

Comments
 (0)