rust/compiler/rustc_trait_selection/src/solve/trait_goals.rs
bors 5639c21fb3 Auto merge of #126505 - compiler-errors:no-vtable, r=lcnr
Only compute vtable information during codegen

This PR removes vtable information from the `Object` and `TraitUpcasting` candidate sources in the trait solvers, and defers the computation of relevant information to `Instance::resolve`. This is because vtables really aren't a thing in the trait world -- they're an implementation detail in codegen.

Previously it was just easiest to tangle this information together since we were already doing the work of looking at all the supertraits in the trait solver, and specifically because we use traits to represent when it's possible to call a method via a vtable (`Object` candidate) and do upcasting (`Unsize` candidate). but I am somewhat suspicious we're doing a *lot* of extra work, especially in polymorphic contexts, so let's see what perf says.
2024-06-16 05:33:49 +00:00

1182 lines
48 KiB
Rust

//! Dealing with trait goals, i.e. `T: Trait<'a, U>`.
use super::assembly::structural_traits::AsyncCallableRelevantTypes;
use super::assembly::{self, structural_traits, Candidate};
use super::{EvalCtxt, GoalSource, SolverMode};
use rustc_data_structures::fx::FxIndexSet;
use rustc_hir::def_id::DefId;
use rustc_hir::{LangItem, Movability};
use rustc_infer::infer::InferCtxt;
use rustc_infer::traits::query::NoSolution;
use rustc_infer::traits::solve::MaybeCause;
use rustc_infer::traits::util::supertraits;
use rustc_middle::bug;
use rustc_middle::traits::solve::inspect::ProbeKind;
use rustc_middle::traits::solve::{CandidateSource, Certainty, Goal, QueryResult};
use rustc_middle::traits::{BuiltinImplSource, Reveal};
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
use rustc_middle::ty::{self, Ty, TyCtxt, Upcast};
use rustc_middle::ty::{TraitPredicate, TypeVisitableExt};
use rustc_span::ErrorGuaranteed;
impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
fn self_ty(self) -> Ty<'tcx> {
self.self_ty()
}
fn trait_ref(self, _: TyCtxt<'tcx>) -> ty::TraitRef<'tcx> {
self.trait_ref
}
fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self {
self.with_self_ty(tcx, self_ty)
}
fn trait_def_id(self, _: TyCtxt<'tcx>) -> DefId {
self.def_id()
}
fn consider_impl_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, TraitPredicate<'tcx>>,
impl_def_id: DefId,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
let tcx = ecx.interner();
let impl_trait_header = tcx.impl_trait_header(impl_def_id).unwrap();
let drcx = DeepRejectCtxt { treat_obligation_params: TreatParams::ForLookup };
if !drcx.args_may_unify(
goal.predicate.trait_ref.args,
impl_trait_header.trait_ref.skip_binder().args,
) {
return Err(NoSolution);
}
// An upper bound of the certainty of this goal, used to lower the certainty
// of reservation impl to ambiguous during coherence.
let impl_polarity = impl_trait_header.polarity;
let maximal_certainty = match (impl_polarity, goal.predicate.polarity) {
// In intercrate mode, this is ambiguous. But outside of intercrate,
// it's not a real impl.
(ty::ImplPolarity::Reservation, _) => match ecx.solver_mode() {
SolverMode::Coherence => Certainty::AMBIGUOUS,
SolverMode::Normal => return Err(NoSolution),
},
// Impl matches polarity
(ty::ImplPolarity::Positive, ty::PredicatePolarity::Positive)
| (ty::ImplPolarity::Negative, ty::PredicatePolarity::Negative) => Certainty::Yes,
// Impl doesn't match polarity
(ty::ImplPolarity::Positive, ty::PredicatePolarity::Negative)
| (ty::ImplPolarity::Negative, ty::PredicatePolarity::Positive) => {
return Err(NoSolution);
}
};
ecx.probe_trait_candidate(CandidateSource::Impl(impl_def_id)).enter(|ecx| {
let impl_args = ecx.fresh_args_for_item(impl_def_id);
ecx.record_impl_args(impl_args);
let impl_trait_ref = impl_trait_header.trait_ref.instantiate(tcx, impl_args);
ecx.eq(goal.param_env, goal.predicate.trait_ref, impl_trait_ref)?;
let where_clause_bounds = tcx
.predicates_of(impl_def_id)
.instantiate(tcx, impl_args)
.predicates
.into_iter()
.map(|pred| goal.with(tcx, pred));
ecx.add_goals(GoalSource::ImplWhereBound, where_clause_bounds);
ecx.evaluate_added_goals_and_make_canonical_response(maximal_certainty)
})
}
fn consider_error_guaranteed_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
_guar: ErrorGuaranteed,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
// FIXME: don't need to enter a probe here.
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
fn probe_and_match_goal_against_assumption(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
source: CandidateSource<'tcx>,
goal: Goal<'tcx, Self>,
assumption: ty::Clause<'tcx>,
then: impl FnOnce(&mut EvalCtxt<'_, InferCtxt<'tcx>>) -> QueryResult<'tcx>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if let Some(trait_clause) = assumption.as_trait_clause() {
if trait_clause.def_id() == goal.predicate.def_id()
&& trait_clause.polarity() == goal.predicate.polarity
{
ecx.probe_trait_candidate(source).enter(|ecx| {
let assumption_trait_pred = ecx.instantiate_binder_with_infer(trait_clause);
ecx.eq(
goal.param_env,
goal.predicate.trait_ref,
assumption_trait_pred.trait_ref,
)?;
then(ecx)
})
} else {
Err(NoSolution)
}
} else {
Err(NoSolution)
}
}
fn consider_auto_trait_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
if let Some(result) = ecx.disqualify_auto_trait_candidate_due_to_possible_impl(goal) {
return result;
}
// Don't call `type_of` on a local TAIT that's in the defining scope,
// since that may require calling `typeck` on the same item we're
// currently type checking, which will result in a fatal cycle that
// ideally we want to avoid, since we can make progress on this goal
// via an alias bound or a locally-inferred hidden type instead.
//
// Also, don't call `type_of` on a TAIT in `Reveal::All` mode, since
// we already normalize the self type in
// `assemble_candidates_after_normalizing_self_ty`, and we'd
// just be registering an identical candidate here.
//
// We always return `Err(NoSolution)` here in `SolverMode::Coherence`
// since we'll always register an ambiguous candidate in
// `assemble_candidates_after_normalizing_self_ty` due to normalizing
// the TAIT.
if let ty::Alias(ty::Opaque, opaque_ty) = goal.predicate.self_ty().kind() {
if matches!(goal.param_env.reveal(), Reveal::All)
|| matches!(ecx.solver_mode(), SolverMode::Coherence)
|| ecx.can_define_opaque_ty(opaque_ty.def_id)
{
return Err(NoSolution);
}
}
ecx.probe_and_evaluate_goal_for_constituent_tys(
CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
goal,
structural_traits::instantiate_constituent_tys_for_auto_trait,
)
}
fn consider_trait_alias_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
let tcx = ecx.interner();
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
let nested_obligations = tcx
.predicates_of(goal.predicate.def_id())
.instantiate(tcx, goal.predicate.trait_ref.args);
// FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`?
ecx.add_goals(
GoalSource::Misc,
nested_obligations.predicates.into_iter().map(|p| goal.with(tcx, p)),
);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}
fn consider_builtin_sized_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
ecx.probe_and_evaluate_goal_for_constituent_tys(
CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
goal,
structural_traits::instantiate_constituent_tys_for_sized_trait,
)
}
fn consider_builtin_copy_clone_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
ecx.probe_and_evaluate_goal_for_constituent_tys(
CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
goal,
structural_traits::instantiate_constituent_tys_for_copy_clone_trait,
)
}
fn consider_builtin_pointer_like_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
// The regions of a type don't affect the size of the type
let tcx = ecx.interner();
// We should erase regions from both the param-env and type, since both
// may have infer regions. Specifically, after canonicalizing and instantiating,
// early bound regions turn into region vars in both the new and old solver.
let key = tcx.erase_regions(goal.param_env.and(goal.predicate.self_ty()));
// But if there are inference variables, we have to wait until it's resolved.
if key.has_non_region_infer() {
return ecx.forced_ambiguity(MaybeCause::Ambiguity);
}
if let Ok(layout) = tcx.layout_of(key)
&& layout.layout.is_pointer_like(&tcx.data_layout)
{
// FIXME: We could make this faster by making a no-constraints response
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
} else {
Err(NoSolution)
}
}
fn consider_builtin_fn_ptr_trait_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
let self_ty = goal.predicate.self_ty();
match goal.predicate.polarity {
// impl FnPtr for FnPtr {}
ty::PredicatePolarity::Positive => {
if self_ty.is_fn_ptr() {
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
} else {
Err(NoSolution)
}
}
// impl !FnPtr for T where T != FnPtr && T is rigid {}
ty::PredicatePolarity::Negative => {
// If a type is rigid and not a fn ptr, then we know for certain
// that it does *not* implement `FnPtr`.
if !self_ty.is_fn_ptr() && self_ty.is_known_rigid() {
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
} else {
Err(NoSolution)
}
}
}
}
fn consider_builtin_fn_trait_candidates(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
goal_kind: ty::ClosureKind,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
let tcx = ecx.interner();
let tupled_inputs_and_output =
match structural_traits::extract_tupled_inputs_and_output_from_callable(
tcx,
goal.predicate.self_ty(),
goal_kind,
)? {
Some(a) => a,
None => {
return ecx.forced_ambiguity(MaybeCause::Ambiguity);
}
};
let output_is_sized_pred = tupled_inputs_and_output.map_bound(|(_, output)| {
ty::TraitRef::new(tcx, tcx.require_lang_item(LangItem::Sized, None), [output])
});
let pred = tupled_inputs_and_output
.map_bound(|(inputs, _)| {
ty::TraitRef::new(tcx, goal.predicate.def_id(), [goal.predicate.self_ty(), inputs])
})
.upcast(tcx);
// A built-in `Fn` impl only holds if the output is sized.
// (FIXME: technically we only need to check this if the type is a fn ptr...)
Self::probe_and_consider_implied_clause(
ecx,
CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
goal,
pred,
[(GoalSource::ImplWhereBound, goal.with(tcx, output_is_sized_pred))],
)
}
fn consider_builtin_async_fn_trait_candidates(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
goal_kind: ty::ClosureKind,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
let tcx = ecx.interner();
let (tupled_inputs_and_output_and_coroutine, nested_preds) =
structural_traits::extract_tupled_inputs_and_output_from_async_callable(
tcx,
goal.predicate.self_ty(),
goal_kind,
// This region doesn't matter because we're throwing away the coroutine type
tcx.lifetimes.re_static,
)?;
let output_is_sized_pred = tupled_inputs_and_output_and_coroutine.map_bound(
|AsyncCallableRelevantTypes { output_coroutine_ty, .. }| {
ty::TraitRef::new(
tcx,
tcx.require_lang_item(LangItem::Sized, None),
[output_coroutine_ty],
)
},
);
let pred = tupled_inputs_and_output_and_coroutine
.map_bound(|AsyncCallableRelevantTypes { tupled_inputs_ty, .. }| {
ty::TraitRef::new(
tcx,
goal.predicate.def_id(),
[goal.predicate.self_ty(), tupled_inputs_ty],
)
})
.upcast(tcx);
// A built-in `AsyncFn` impl only holds if the output is sized.
// (FIXME: technically we only need to check this if the type is a fn ptr...)
Self::probe_and_consider_implied_clause(
ecx,
CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
goal,
pred,
[goal.with(tcx, output_is_sized_pred)]
.into_iter()
.chain(nested_preds.into_iter().map(|pred| goal.with(tcx, pred)))
.map(|goal| (GoalSource::ImplWhereBound, goal)),
)
}
fn consider_builtin_async_fn_kind_helper_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
let [closure_fn_kind_ty, goal_kind_ty] = **goal.predicate.trait_ref.args else {
bug!();
};
let Some(closure_kind) = closure_fn_kind_ty.expect_ty().to_opt_closure_kind() else {
// We don't need to worry about the self type being an infer var.
return Err(NoSolution);
};
let goal_kind = goal_kind_ty.expect_ty().to_opt_closure_kind().unwrap();
if closure_kind.extends(goal_kind) {
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
} else {
Err(NoSolution)
}
}
/// ```rust, ignore (not valid rust syntax)
/// impl Tuple for () {}
/// impl Tuple for (T1,) {}
/// impl Tuple for (T1, T2) {}
/// impl Tuple for (T1, .., Tn) {}
/// ```
fn consider_builtin_tuple_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
if let ty::Tuple(..) = goal.predicate.self_ty().kind() {
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
} else {
Err(NoSolution)
}
}
fn consider_builtin_pointee_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
fn consider_builtin_future_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
let ty::Coroutine(def_id, _) = *goal.predicate.self_ty().kind() else {
return Err(NoSolution);
};
// Coroutines are not futures unless they come from `async` desugaring
let tcx = ecx.interner();
if !tcx.coroutine_is_async(def_id) {
return Err(NoSolution);
}
// Async coroutine unconditionally implement `Future`
// Technically, we need to check that the future output type is Sized,
// but that's already proven by the coroutine being WF.
// FIXME: use `consider_implied`
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
fn consider_builtin_iterator_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
let ty::Coroutine(def_id, _) = *goal.predicate.self_ty().kind() else {
return Err(NoSolution);
};
// Coroutines are not iterators unless they come from `gen` desugaring
let tcx = ecx.interner();
if !tcx.coroutine_is_gen(def_id) {
return Err(NoSolution);
}
// Gen coroutines unconditionally implement `Iterator`
// Technically, we need to check that the iterator output type is Sized,
// but that's already proven by the coroutines being WF.
// FIXME: use `consider_implied`
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
fn consider_builtin_fused_iterator_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
let ty::Coroutine(def_id, _) = *goal.predicate.self_ty().kind() else {
return Err(NoSolution);
};
// Coroutines are not iterators unless they come from `gen` desugaring
let tcx = ecx.interner();
if !tcx.coroutine_is_gen(def_id) {
return Err(NoSolution);
}
// Gen coroutines unconditionally implement `FusedIterator`
// FIXME: use `consider_implied`
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
fn consider_builtin_async_iterator_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
let ty::Coroutine(def_id, _) = *goal.predicate.self_ty().kind() else {
return Err(NoSolution);
};
// Coroutines are not iterators unless they come from `gen` desugaring
let tcx = ecx.interner();
if !tcx.coroutine_is_async_gen(def_id) {
return Err(NoSolution);
}
// Gen coroutines unconditionally implement `Iterator`
// Technically, we need to check that the iterator output type is Sized,
// but that's already proven by the coroutines being WF.
// FIXME: use `consider_implied`
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
fn consider_builtin_coroutine_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
let self_ty = goal.predicate.self_ty();
let ty::Coroutine(def_id, args) = *self_ty.kind() else {
return Err(NoSolution);
};
// `async`-desugared coroutines do not implement the coroutine trait
let tcx = ecx.interner();
if !tcx.is_general_coroutine(def_id) {
return Err(NoSolution);
}
let coroutine = args.as_coroutine();
Self::probe_and_consider_implied_clause(
ecx,
CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
goal,
ty::TraitRef::new(tcx, goal.predicate.def_id(), [self_ty, coroutine.resume_ty()])
.upcast(tcx),
// Technically, we need to check that the coroutine types are Sized,
// but that's already proven by the coroutine being WF.
[],
)
}
fn consider_builtin_discriminant_kind_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
// `DiscriminantKind` is automatically implemented for every type.
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
fn consider_builtin_async_destruct_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
// `AsyncDestruct` is automatically implemented for every type.
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
fn consider_builtin_destruct_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
// FIXME(-Znext-solver): Implement this when we get const working in the new solver
// `Destruct` is automatically implemented for every type in
// non-const environments.
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
fn consider_builtin_transmute_candidate(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return Err(NoSolution);
}
// `rustc_transmute` does not have support for type or const params
if goal.has_non_region_placeholders() {
return Err(NoSolution);
}
// Erase regions because we compute layouts in `rustc_transmute`,
// which will ICE for region vars.
let args = ecx.interner().erase_regions(goal.predicate.trait_ref.args);
let Some(assume) =
rustc_transmute::Assume::from_const(ecx.interner(), goal.param_env, args.const_at(2))
else {
return Err(NoSolution);
};
// FIXME: This actually should destructure the `Result` we get from transmutability and
// register candiates. We probably need to register >1 since we may have an OR of ANDs.
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
let certainty = ecx.is_transmutable(
rustc_transmute::Types { dst: args.type_at(0), src: args.type_at(1) },
assume,
)?;
ecx.evaluate_added_goals_and_make_canonical_response(certainty)
})
}
/// ```ignore (builtin impl example)
/// trait Trait {
/// fn foo(&self);
/// }
/// // results in the following builtin impl
/// impl<'a, T: Trait + 'a> Unsize<dyn Trait + 'a> for T {}
/// ```
fn consider_structural_builtin_unsize_candidates(
ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
goal: Goal<'tcx, Self>,
) -> Vec<Candidate<TyCtxt<'tcx>>> {
if goal.predicate.polarity != ty::PredicatePolarity::Positive {
return vec![];
}
let result_to_single = |result| match result {
Ok(resp) => vec![resp],
Err(NoSolution) => vec![],
};
ecx.probe(|_| ProbeKind::UnsizeAssembly).enter(|ecx| {
let a_ty = goal.predicate.self_ty();
// We need to normalize the b_ty since it's matched structurally
// in the other functions below.
let Ok(b_ty) = ecx.structurally_normalize_ty(
goal.param_env,
goal.predicate.trait_ref.args.type_at(1),
) else {
return vec![];
};
let goal = goal.with(ecx.interner(), (a_ty, b_ty));
match (a_ty.kind(), b_ty.kind()) {
(ty::Infer(ty::TyVar(..)), ..) => bug!("unexpected infer {a_ty:?} {b_ty:?}"),
(_, ty::Infer(ty::TyVar(..))) => {
result_to_single(ecx.forced_ambiguity(MaybeCause::Ambiguity))
}
// Trait upcasting, or `dyn Trait + Auto + 'a` -> `dyn Trait + 'b`.
(
&ty::Dynamic(a_data, a_region, ty::Dyn),
&ty::Dynamic(b_data, b_region, ty::Dyn),
) => ecx.consider_builtin_dyn_upcast_candidates(
goal, a_data, a_region, b_data, b_region,
),
// `T` -> `dyn Trait` unsizing.
(_, &ty::Dynamic(b_region, b_data, ty::Dyn)) => result_to_single(
ecx.consider_builtin_unsize_to_dyn_candidate(goal, b_region, b_data),
),
// `[T; N]` -> `[T]` unsizing
(&ty::Array(a_elem_ty, ..), &ty::Slice(b_elem_ty)) => {
result_to_single(ecx.consider_builtin_array_unsize(goal, a_elem_ty, b_elem_ty))
}
// `Struct<T>` -> `Struct<U>` where `T: Unsize<U>`
(&ty::Adt(a_def, a_args), &ty::Adt(b_def, b_args))
if a_def.is_struct() && a_def == b_def =>
{
result_to_single(
ecx.consider_builtin_struct_unsize(goal, a_def, a_args, b_args),
)
}
// `(A, B, T)` -> `(A, B, U)` where `T: Unsize<U>`
(&ty::Tuple(a_tys), &ty::Tuple(b_tys))
if a_tys.len() == b_tys.len() && !a_tys.is_empty() =>
{
result_to_single(ecx.consider_builtin_tuple_unsize(goal, a_tys, b_tys))
}
_ => vec![],
}
})
}
}
impl<'tcx> EvalCtxt<'_, InferCtxt<'tcx>> {
/// Trait upcasting allows for coercions between trait objects:
/// ```ignore (builtin impl example)
/// trait Super {}
/// trait Trait: Super {}
/// // results in builtin impls upcasting to a super trait
/// impl<'a, 'b: 'a> Unsize<dyn Super + 'a> for dyn Trait + 'b {}
/// // and impls removing auto trait bounds.
/// impl<'a, 'b: 'a> Unsize<dyn Trait + 'a> for dyn Trait + Send + 'b {}
/// ```
fn consider_builtin_dyn_upcast_candidates(
&mut self,
goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>,
a_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
a_region: ty::Region<'tcx>,
b_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
b_region: ty::Region<'tcx>,
) -> Vec<Candidate<TyCtxt<'tcx>>> {
let tcx = self.interner();
let Goal { predicate: (a_ty, _b_ty), .. } = goal;
let mut responses = vec![];
// If the principal def ids match (or are both none), then we're not doing
// trait upcasting. We're just removing auto traits (or shortening the lifetime).
if a_data.principal_def_id() == b_data.principal_def_id() {
responses.extend(self.consider_builtin_upcast_to_principal(
goal,
CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
a_data,
a_region,
b_data,
b_region,
a_data.principal(),
));
} else if let Some(a_principal) = a_data.principal() {
for new_a_principal in supertraits(tcx, a_principal.with_self_ty(tcx, a_ty)).skip(1) {
responses.extend(self.consider_builtin_upcast_to_principal(
goal,
CandidateSource::BuiltinImpl(BuiltinImplSource::TraitUpcasting),
a_data,
a_region,
b_data,
b_region,
Some(new_a_principal.map_bound(|trait_ref| {
ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)
})),
));
}
}
responses
}
fn consider_builtin_unsize_to_dyn_candidate(
&mut self,
goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>,
b_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
b_region: ty::Region<'tcx>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
let tcx = self.interner();
let Goal { predicate: (a_ty, _), .. } = goal;
// Can only unsize to an object-safe trait.
if b_data.principal_def_id().is_some_and(|def_id| !tcx.is_object_safe(def_id)) {
return Err(NoSolution);
}
self.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
// Check that the type implements all of the predicates of the trait object.
// (i.e. the principal, all of the associated types match, and any auto traits)
ecx.add_goals(
GoalSource::ImplWhereBound,
b_data.iter().map(|pred| goal.with(tcx, pred.with_self_ty(tcx, a_ty))),
);
// The type must be `Sized` to be unsized.
ecx.add_goal(
GoalSource::ImplWhereBound,
goal.with(
tcx,
ty::TraitRef::new(tcx, tcx.require_lang_item(LangItem::Sized, None), [a_ty]),
),
);
// The type must outlive the lifetime of the `dyn` we're unsizing into.
ecx.add_goal(GoalSource::Misc, goal.with(tcx, ty::OutlivesPredicate(a_ty, b_region)));
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}
fn consider_builtin_upcast_to_principal(
&mut self,
goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>,
source: CandidateSource<'tcx>,
a_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
a_region: ty::Region<'tcx>,
b_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
b_region: ty::Region<'tcx>,
upcast_principal: Option<ty::PolyExistentialTraitRef<'tcx>>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
let param_env = goal.param_env;
// We may upcast to auto traits that are either explicitly listed in
// the object type's bounds, or implied by the principal trait ref's
// supertraits.
let a_auto_traits: FxIndexSet<DefId> = a_data
.auto_traits()
.chain(a_data.principal_def_id().into_iter().flat_map(|principal_def_id| {
self.interner()
.supertrait_def_ids(principal_def_id)
.filter(|def_id| self.interner().trait_is_auto(*def_id))
}))
.collect();
// More than one projection in a_ty's bounds may match the projection
// in b_ty's bound. Use this to first determine *which* apply without
// having any inference side-effects. We process obligations because
// unification may initially succeed due to deferred projection equality.
let projection_may_match =
|ecx: &mut EvalCtxt<'_, InferCtxt<'tcx>>,
source_projection: ty::PolyExistentialProjection<'tcx>,
target_projection: ty::PolyExistentialProjection<'tcx>| {
source_projection.item_def_id() == target_projection.item_def_id()
&& ecx
.probe(|_| ProbeKind::UpcastProjectionCompatibility)
.enter(|ecx| -> Result<(), NoSolution> {
ecx.eq(param_env, source_projection, target_projection)?;
let _ = ecx.try_evaluate_added_goals()?;
Ok(())
})
.is_ok()
};
self.probe_trait_candidate(source).enter(|ecx| {
for bound in b_data {
match bound.skip_binder() {
// Check that a's supertrait (upcast_principal) is compatible
// with the target (b_ty).
ty::ExistentialPredicate::Trait(target_principal) => {
ecx.eq(
param_env,
upcast_principal.unwrap(),
bound.rebind(target_principal),
)?;
}
// Check that b_ty's projection is satisfied by exactly one of
// a_ty's projections. First, we look through the list to see if
// any match. If not, error. Then, if *more* than one matches, we
// return ambiguity. Otherwise, if exactly one matches, equate
// it with b_ty's projection.
ty::ExistentialPredicate::Projection(target_projection) => {
let target_projection = bound.rebind(target_projection);
let mut matching_projections =
a_data.projection_bounds().filter(|source_projection| {
projection_may_match(ecx, *source_projection, target_projection)
});
let Some(source_projection) = matching_projections.next() else {
return Err(NoSolution);
};
if matching_projections.next().is_some() {
return ecx.evaluate_added_goals_and_make_canonical_response(
Certainty::AMBIGUOUS,
);
}
ecx.eq(param_env, source_projection, target_projection)?;
}
// Check that b_ty's auto traits are present in a_ty's bounds.
ty::ExistentialPredicate::AutoTrait(def_id) => {
if !a_auto_traits.contains(&def_id) {
return Err(NoSolution);
}
}
}
}
// Also require that a_ty's lifetime outlives b_ty's lifetime.
ecx.add_goal(
GoalSource::ImplWhereBound,
Goal::new(
ecx.interner(),
param_env,
ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region)),
),
);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}
/// We have the following builtin impls for arrays:
/// ```ignore (builtin impl example)
/// impl<T: ?Sized, const N: usize> Unsize<[T]> for [T; N] {}
/// ```
/// While the impl itself could theoretically not be builtin,
/// the actual unsizing behavior is builtin. Its also easier to
/// make all impls of `Unsize` builtin as we're able to use
/// `#[rustc_deny_explicit_impl]` in this case.
fn consider_builtin_array_unsize(
&mut self,
goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>,
a_elem_ty: Ty<'tcx>,
b_elem_ty: Ty<'tcx>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
self.eq(goal.param_env, a_elem_ty, b_elem_ty)?;
self.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
/// We generate a builtin `Unsize` impls for structs with generic parameters only
/// mentioned by the last field.
/// ```ignore (builtin impl example)
/// struct Foo<T, U: ?Sized> {
/// sized_field: Vec<T>,
/// unsizable: Box<U>,
/// }
/// // results in the following builtin impl
/// impl<T: ?Sized, U: ?Sized, V: ?Sized> Unsize<Foo<T, V>> for Foo<T, U>
/// where
/// Box<U>: Unsize<Box<V>>,
/// {}
/// ```
fn consider_builtin_struct_unsize(
&mut self,
goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>,
def: ty::AdtDef<'tcx>,
a_args: ty::GenericArgsRef<'tcx>,
b_args: ty::GenericArgsRef<'tcx>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
let tcx = self.interner();
let Goal { predicate: (_a_ty, b_ty), .. } = goal;
let unsizing_params = tcx.unsizing_params_for_adt(def.did());
// We must be unsizing some type parameters. This also implies
// that the struct has a tail field.
if unsizing_params.is_empty() {
return Err(NoSolution);
}
let tail_field = def.non_enum_variant().tail();
let tail_field_ty = tcx.type_of(tail_field.did);
let a_tail_ty = tail_field_ty.instantiate(tcx, a_args);
let b_tail_ty = tail_field_ty.instantiate(tcx, b_args);
// Instantiate just the unsizing params from B into A. The type after
// this instantiation must be equal to B. This is so we don't unsize
// unrelated type parameters.
let new_a_args = tcx.mk_args_from_iter(
a_args
.iter()
.enumerate()
.map(|(i, a)| if unsizing_params.contains(i as u32) { b_args[i] } else { a }),
);
let unsized_a_ty = Ty::new_adt(tcx, def, new_a_args);
// Finally, we require that `TailA: Unsize<TailB>` for the tail field
// types.
self.eq(goal.param_env, unsized_a_ty, b_ty)?;
self.add_goal(
GoalSource::ImplWhereBound,
goal.with(
tcx,
ty::TraitRef::new(
tcx,
tcx.require_lang_item(LangItem::Unsize, None),
[a_tail_ty, b_tail_ty],
),
),
);
self.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
/// We generate the following builtin impl for tuples of all sizes.
///
/// This impl is still unstable and we emit a feature error when it
/// when it is used by a coercion.
/// ```ignore (builtin impl example)
/// impl<T: ?Sized, U: ?Sized, V: ?Sized> Unsize<(T, V)> for (T, U)
/// where
/// U: Unsize<V>,
/// {}
/// ```
fn consider_builtin_tuple_unsize(
&mut self,
goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>,
a_tys: &'tcx ty::List<Ty<'tcx>>,
b_tys: &'tcx ty::List<Ty<'tcx>>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
let tcx = self.interner();
let Goal { predicate: (_a_ty, b_ty), .. } = goal;
let (&a_last_ty, a_rest_tys) = a_tys.split_last().unwrap();
let &b_last_ty = b_tys.last().unwrap();
// Instantiate just the tail field of B., and require that they're equal.
let unsized_a_ty =
Ty::new_tup_from_iter(tcx, a_rest_tys.iter().copied().chain([b_last_ty]));
self.eq(goal.param_env, unsized_a_ty, b_ty)?;
// Similar to ADTs, require that we can unsize the tail.
self.add_goal(
GoalSource::ImplWhereBound,
goal.with(
tcx,
ty::TraitRef::new(
tcx,
tcx.require_lang_item(LangItem::Unsize, None),
[a_last_ty, b_last_ty],
),
),
);
self.probe_builtin_trait_candidate(BuiltinImplSource::TupleUnsizing)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
// Return `Some` if there is an impl (built-in or user provided) that may
// hold for the self type of the goal, which for coherence and soundness
// purposes must disqualify the built-in auto impl assembled by considering
// the type's constituent types.
fn disqualify_auto_trait_candidate_due_to_possible_impl(
&mut self,
goal: Goal<'tcx, TraitPredicate<'tcx>>,
) -> Option<Result<Candidate<TyCtxt<'tcx>>, NoSolution>> {
let self_ty = goal.predicate.self_ty();
match *self_ty.kind() {
// Stall int and float vars until they are resolved to a concrete
// numerical type. That's because the check for impls below treats
// int vars as matching any impl. Even if we filtered such impls,
// we probably don't want to treat an `impl !AutoTrait for i32` as
// disqualifying the built-in auto impl for `i64: AutoTrait` either.
ty::Infer(ty::IntVar(_) | ty::FloatVar(_)) => {
Some(self.forced_ambiguity(MaybeCause::Ambiguity))
}
// These types cannot be structurally decomposed into constituent
// types, and therefore have no built-in auto impl.
ty::Dynamic(..)
| ty::Param(..)
| ty::Foreign(..)
| ty::Alias(ty::Projection | ty::Weak | ty::Inherent, ..)
| ty::Placeholder(..) => Some(Err(NoSolution)),
ty::Infer(_) | ty::Bound(_, _) => bug!("unexpected type `{self_ty}`"),
// Coroutines have one special built-in candidate, `Unpin`, which
// takes precedence over the structural auto trait candidate being
// assembled.
ty::Coroutine(def_id, _)
if self.interner().is_lang_item(goal.predicate.def_id(), LangItem::Unpin) =>
{
match self.interner().coroutine_movability(def_id) {
Movability::Static => Some(Err(NoSolution)),
Movability::Movable => Some(
self.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}),
),
}
}
// If we still have an alias here, it must be rigid. For opaques, it's always
// okay to consider auto traits because that'll reveal its hidden type. For
// non-opaque aliases, we will not assemble any candidates since there's no way
// to further look into its type.
ty::Alias(..) => None,
// For rigid types, any possible implementation that could apply to
// the type (even if after unification and processing nested goals
// it does not hold) will disqualify the built-in auto impl.
//
// This differs from the current stable behavior and fixes #84857.
// Due to breakage found via crater, we currently instead lint
// patterns which can be used to exploit this unsoundness on stable,
// see #93367 for more details.
ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Str
| ty::Array(_, _)
| ty::Pat(_, _)
| ty::Slice(_)
| ty::RawPtr(_, _)
| ty::Ref(_, _, _)
| ty::FnDef(_, _)
| ty::FnPtr(_)
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::Coroutine(_, _)
| ty::CoroutineWitness(..)
| ty::Never
| ty::Tuple(_)
| ty::Adt(_, _) => {
let mut disqualifying_impl = None;
self.interner().for_each_relevant_impl(
goal.predicate.def_id(),
goal.predicate.self_ty(),
|impl_def_id| {
disqualifying_impl = Some(impl_def_id);
},
);
if let Some(def_id) = disqualifying_impl {
trace!(?def_id, ?goal, "disqualified auto-trait implementation");
// No need to actually consider the candidate here,
// since we do that in `consider_impl_candidate`.
return Some(Err(NoSolution));
} else {
None
}
}
ty::Error(_) => None,
}
}
/// Convenience function for traits that are structural, i.e. that only
/// have nested subgoals that only change the self type. Unlike other
/// evaluate-like helpers, this does a probe, so it doesn't need to be
/// wrapped in one.
fn probe_and_evaluate_goal_for_constituent_tys(
&mut self,
source: CandidateSource<'tcx>,
goal: Goal<'tcx, TraitPredicate<'tcx>>,
constituent_tys: impl Fn(
&EvalCtxt<'_, InferCtxt<'tcx>>,
Ty<'tcx>,
) -> Result<Vec<ty::Binder<'tcx, Ty<'tcx>>>, NoSolution>,
) -> Result<Candidate<TyCtxt<'tcx>>, NoSolution> {
self.probe_trait_candidate(source).enter(|ecx| {
ecx.add_goals(
GoalSource::ImplWhereBound,
constituent_tys(ecx, goal.predicate.self_ty())?
.into_iter()
.map(|ty| {
ecx.enter_forall(ty, |ty| {
goal.with(
ecx.interner(),
goal.predicate.with_self_ty(ecx.interner(), ty),
)
})
})
.collect::<Vec<_>>(),
);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}
#[instrument(level = "trace", skip(self))]
pub(super) fn compute_trait_goal(
&mut self,
goal: Goal<'tcx, TraitPredicate<'tcx>>,
) -> QueryResult<'tcx> {
let candidates = self.assemble_and_evaluate_candidates(goal);
self.merge_candidates(candidates)
}
}