Auto merge of #141731 - compiler-errors:tweak-fast-path-trait, r=lcnr

Tweak fast path trait handling

(1.) Make it more sound by considering polarity (lol)

(2.) Make it more general, by considering higher-ranked size/copy/clone

(2.) Make it less observable, by only doing copy/clone fast path if there are no regions involved

r? lcnr
This commit is contained in:
bors
2025-06-01 10:59:38 +00:00
5 changed files with 54 additions and 34 deletions

View File

@@ -1882,10 +1882,8 @@ impl<'tcx> Ty<'tcx> {
// Needs normalization or revealing to determine, so no is the safe answer. // Needs normalization or revealing to determine, so no is the safe answer.
ty::Alias(..) => false, ty::Alias(..) => false,
ty::Param(..) | ty::Placeholder(..) | ty::Infer(..) | ty::Error(..) => false, ty::Param(..) | ty::Placeholder(..) | ty::Bound(..) | ty::Infer(..) | ty::Error(..) => {
false
ty::Bound(..) => {
bug!("`is_trivially_pure_clone_copy` applied to unexpected type: {:?}", self);
} }
} }
} }

View File

@@ -3,8 +3,6 @@ use std::ops::Deref;
use rustc_type_ir::solve::{Certainty, Goal, NoSolution}; use rustc_type_ir::solve::{Certainty, Goal, NoSolution};
use rustc_type_ir::{self as ty, InferCtxtLike, Interner, TypeFoldable}; use rustc_type_ir::{self as ty, InferCtxtLike, Interner, TypeFoldable};
use crate::solve::HasChanged;
pub trait SolverDelegate: Deref<Target = Self::Infcx> + Sized { pub trait SolverDelegate: Deref<Target = Self::Infcx> + Sized {
type Infcx: InferCtxtLike<Interner = Self::Interner>; type Infcx: InferCtxtLike<Interner = Self::Interner>;
type Interner: Interner; type Interner: Interner;
@@ -23,7 +21,7 @@ pub trait SolverDelegate: Deref<Target = Self::Infcx> + Sized {
&self, &self,
goal: Goal<Self::Interner, <Self::Interner as Interner>::Predicate>, goal: Goal<Self::Interner, <Self::Interner as Interner>::Predicate>,
span: <Self::Interner as Interner>::Span, span: <Self::Interner as Interner>::Span,
) -> Option<HasChanged>; ) -> Option<Certainty>;
fn fresh_var_for_kind_with_span( fn fresh_var_for_kind_with_span(
&self, &self,

View File

@@ -671,10 +671,13 @@ where
// If this loop did not result in any progress, what's our final certainty. // If this loop did not result in any progress, what's our final certainty.
let mut unchanged_certainty = Some(Certainty::Yes); let mut unchanged_certainty = Some(Certainty::Yes);
for (source, goal, stalled_on) in mem::take(&mut self.nested_goals) { for (source, goal, stalled_on) in mem::take(&mut self.nested_goals) {
if let Some(has_changed) = self.delegate.compute_goal_fast_path(goal, self.origin_span) if let Some(certainty) = self.delegate.compute_goal_fast_path(goal, self.origin_span) {
{ match certainty {
if matches!(has_changed, HasChanged::Yes) { Certainty::Yes => {}
unchanged_certainty = None; Certainty::Maybe(_) => {
self.nested_goals.push((source, goal, None));
unchanged_certainty = unchanged_certainty.map(|c| c.and(certainty));
}
} }
continue; continue;
} }

View File

@@ -11,8 +11,9 @@ use rustc_infer::infer::{InferCtxt, RegionVariableOrigin, SubregionOrigin, TyCtx
use rustc_infer::traits::solve::Goal; use rustc_infer::traits::solve::Goal;
use rustc_middle::traits::query::NoSolution; use rustc_middle::traits::query::NoSolution;
use rustc_middle::traits::solve::Certainty; use rustc_middle::traits::solve::Certainty;
use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeVisitableExt as _, TypingMode}; use rustc_middle::ty::{
use rustc_next_trait_solver::solve::HasChanged; self, Ty, TyCtxt, TypeFlags, TypeFoldable, TypeVisitableExt as _, TypingMode,
};
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span}; use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span};
use crate::traits::{EvaluateConstErr, ObligationCause, specialization_graph}; use crate::traits::{EvaluateConstErr, ObligationCause, specialization_graph};
@@ -61,11 +62,41 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate<
&self, &self,
goal: Goal<'tcx, ty::Predicate<'tcx>>, goal: Goal<'tcx, ty::Predicate<'tcx>>,
span: Span, span: Span,
) -> Option<HasChanged> { ) -> Option<Certainty> {
if let Some(trait_pred) = goal.predicate.as_trait_clause() {
if trait_pred.polarity() == ty::PredicatePolarity::Positive {
match self.0.tcx.as_lang_item(trait_pred.def_id()) {
Some(LangItem::Sized)
if self
.resolve_vars_if_possible(trait_pred.self_ty().skip_binder())
.is_trivially_sized(self.0.tcx) =>
{
return Some(Certainty::Yes);
}
Some(LangItem::Copy | LangItem::Clone) => {
let self_ty =
self.resolve_vars_if_possible(trait_pred.self_ty().skip_binder());
// Unlike `Sized` traits, which always prefer the built-in impl,
// `Copy`/`Clone` may be shadowed by a param-env candidate which
// could force a lifetime error or guide inference. While that's
// not generally desirable, it is observable, so for now let's
// ignore this fast path for types that have regions or infer.
if !self_ty
.has_type_flags(TypeFlags::HAS_FREE_REGIONS | TypeFlags::HAS_INFER)
&& self_ty.is_trivially_pure_clone_copy()
{
return Some(Certainty::Yes);
}
}
_ => {}
}
}
}
let pred = goal.predicate.kind(); let pred = goal.predicate.kind();
match pred.no_bound_vars()? { match pred.no_bound_vars()? {
ty::PredicateKind::DynCompatible(def_id) if self.0.tcx.is_dyn_compatible(def_id) => { ty::PredicateKind::DynCompatible(def_id) if self.0.tcx.is_dyn_compatible(def_id) => {
Some(HasChanged::No) Some(Certainty::Yes)
} }
ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(outlives)) => { ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(outlives)) => {
self.0.sub_regions( self.0.sub_regions(
@@ -73,7 +104,7 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate<
outlives.1, outlives.1,
outlives.0, outlives.0,
); );
Some(HasChanged::No) Some(Certainty::Yes)
} }
ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(outlives)) => { ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(outlives)) => {
self.0.register_type_outlives_constraint( self.0.register_type_outlives_constraint(
@@ -82,22 +113,7 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate<
&ObligationCause::dummy_with_span(span), &ObligationCause::dummy_with_span(span),
); );
Some(HasChanged::No) Some(Certainty::Yes)
}
ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)) => {
match self.0.tcx.as_lang_item(trait_pred.def_id()) {
Some(LangItem::Sized)
if trait_pred.self_ty().is_trivially_sized(self.0.tcx) =>
{
Some(HasChanged::No)
}
Some(LangItem::Copy | LangItem::Clone)
if trait_pred.self_ty().is_trivially_pure_clone_copy() =>
{
Some(HasChanged::No)
}
_ => None,
}
} }
_ => None, _ => None,
} }

View File

@@ -195,10 +195,15 @@ where
let goal = obligation.as_goal(); let goal = obligation.as_goal();
let delegate = <&SolverDelegate<'tcx>>::from(infcx); let delegate = <&SolverDelegate<'tcx>>::from(infcx);
if let Some(fast_path_has_changed) = if let Some(certainty) =
delegate.compute_goal_fast_path(goal, obligation.cause.span) delegate.compute_goal_fast_path(goal, obligation.cause.span)
{ {
any_changed |= matches!(fast_path_has_changed, HasChanged::Yes); match certainty {
Certainty::Yes => {}
Certainty::Maybe(_) => {
self.obligations.register(obligation, None);
}
}
continue; continue;
} }