Skip to content

[PERF] Rework unsizing coercions in new solver #141926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
266 changes: 162 additions & 104 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_attr_data_structures::InlineAttr;
use rustc_errors::codes::*;
use rustc_errors::{Applicability, Diag, struct_span_code_err};
use rustc_hir as hir;
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::{Coercion, DefineOpaqueTypes, InferOk, InferResult};
Expand All @@ -57,6 +57,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, NormalizeExt, ObligationCause, ObligationCauseCode, ObligationCtxt,
Expand Down Expand Up @@ -593,118 +595,128 @@ 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])
)];

// 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.select_where_possible().is_empty() {
return Err(TypeError::Mismatch);
if self.next_trait_solver() {
coercion.obligations.push(obligation);

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

// 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)
}
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);
// 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.select_where_possible().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);
}
} else {
debug!("coerce_unsized: early return - ambiguous");
}
Err(traits::Unimplemented) => {
debug!("coerce_unsized: early return - can't prove obligation");
return Err(TypeError::Mismatch);
}
}
Err(traits::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.
}
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(impl_source)) => queue.extend(impl_source.nested_obligations()),
Ok(Some(impl_source)) => queue.extend(impl_source.nested_obligations()),
}
}
}

Expand Down Expand Up @@ -2022,3 +2034,49 @@ impl AsCoercionSite for hir::Arm<'_> {
self.body
}
}

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(())
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use rustc_middle::traits::solve::GoalSource;
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
use rustc_span::Span;
use rustc_trait_selection::solve::inspect::{
InspectConfig, InspectGoal, ProofTreeInferCtxtExt, ProofTreeVisitor,
InferCtxtProofTreeExt, InspectConfig, InspectGoal, ProofTreeVisitor,
};
use tracing::{debug, instrument, trace};

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_trait_selection/src/solve/fulfill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use tracing::instrument;
use self::derive_errors::*;
use super::Certainty;
use super::delegate::SolverDelegate;
use super::inspect::{self, ProofTreeInferCtxtExt};
use super::inspect::{self, InferCtxtProofTreeExt};
use crate::traits::{FulfillmentError, ScrubbedTraitError};

mod derive_errors;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use rustc_next_trait_solver::solve::{
use tracing::{instrument, trace};

use crate::solve::delegate::SolverDelegate;
use crate::solve::inspect::{self, ProofTreeInferCtxtExt, ProofTreeVisitor};
use crate::solve::inspect::{self, InferCtxtProofTreeExt, ProofTreeVisitor};
use crate::solve::{Certainty, deeply_normalize_for_diagnostics};
use crate::traits::{FulfillmentError, FulfillmentErrorCode, wf};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ pub trait ProofTreeVisitor<'tcx> {
fn visit_goal(&mut self, goal: &InspectGoal<'_, 'tcx>) -> Self::Result;
}

#[extension(pub trait ProofTreeInferCtxtExt<'tcx>)]
#[extension(pub trait InferCtxtProofTreeExt<'tcx>)]
impl<'tcx> InferCtxt<'tcx> {
fn visit_proof_tree<V: ProofTreeVisitor<'tcx>>(
&self,
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_trait_selection/src/solve/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use rustc_macros::extension;
use rustc_middle::{bug, span_bug};
use rustc_span::Span;

use crate::solve::inspect::{self, ProofTreeInferCtxtExt};
use crate::solve::inspect::{self, InferCtxtProofTreeExt};

#[extension(pub trait InferCtxtSelectExt<'tcx>)]
impl<'tcx> InferCtxt<'tcx> {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_trait_selection/src/traits/coherence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,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, ProofTreeInferCtxtExt, 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;
Expand Down
Loading