From 19e847b30ff459aeac1905b205072b3bb78d22bc Mon Sep 17 00:00:00 2001 From: Makro Date: Mon, 13 Jul 2026 22:34:51 +0000 Subject: [PATCH] perf: fast-reject method probe candidates by self type head Every method-call probe runs a by-value candidate pass before the autoref passes, and for the overwhelmingly common &self/&mut self methods that pass scans all name-matched candidates, paying for a snapshot, fresh args and normalization per candidate just to fail the self type unification. Compare the simplified head of the receiver against the simplified head of the method's declared self parameter first; two concrete, different heads cannot unify. Bare Self, aliases and non-method items fall through to the full check, and path-mode (UFCS) probes are exempt since they relate against the impl self type instead. --- compiler/rustc_hir_typeck/src/method/probe.rs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index ce22ab937391e..a50e69369617a 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -15,7 +15,7 @@ use rustc_infer::traits::{ObligationCauseCode, PredicateObligation, query}; use rustc_macros::Diagnostic; use rustc_middle::middle::stability; use rustc_middle::ty::elaborate::supertrait_def_ids; -use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams, simplify_type}; +use rustc_middle::ty::fast_reject::{DeepRejectCtxt, SimplifiedType, TreatParams, simplify_type}; use rustc_middle::ty::{ self, AssocContainer, AssocItem, GenericArgs, GenericArgsRef, GenericParamDefKind, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt, Unnormalized, Upcast, @@ -1709,6 +1709,36 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { None } + /// A cheap pre-filter: if both the receiver type and the candidate + /// method's declared self type simplify to concrete, different type + /// heads, the full `consider_probe` relation cannot succeed. This makes + /// the by-value probe pass nearly free for `&self`/`&mut self` methods — + /// the overwhelmingly common kind — which would otherwise pay for a + /// snapshot, fresh args and normalization per candidate just to fail the + /// self type unification. + fn candidate_self_head_may_unify( + &self, + self_ty_head: Option, + candidate: &Candidate<'tcx>, + ) -> bool { + // Only method-call probes relate the receiver against the method's + // transformed self type; path probes (UFCS) relate against the + // impl's self type, where the method's own self shape is irrelevant. + if self.mode != Mode::MethodCall { + return true; + } + let Some(self_ty_head) = self_ty_head else { return true }; + if !candidate.item.is_method() { + return true; + } + let declared_self = + self.tcx.fn_sig(candidate.item.def_id).skip_binder().skip_binder().inputs()[0]; + match simplify_type(self.tcx, declared_self, TreatParams::InstantiateWithInfer) { + Some(declared_head) => declared_head == self_ty_head, + None => true, + } + } + fn consider_candidates( &self, self_ty: Ty<'tcx>, @@ -1717,6 +1747,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { pick_diag_hints: &mut PickDiagHints<'_, 'tcx>, pick_constraints: Option<&PickConstraintsForShadowed>, ) -> Option> { + let self_ty_head = simplify_type(self.tcx, self_ty, TreatParams::InstantiateWithInfer); let mut applicable_candidates: Vec<_> = candidates .iter() .filter(|candidate| { @@ -1724,6 +1755,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { .map(|pick_constraints| pick_constraints.candidate_may_shadow(&candidate)) .unwrap_or(true) }) + .filter(|candidate| self.candidate_self_head_may_unify(self_ty_head, candidate)) .map(|probe| { ( probe,