From 5b8f3c89b83d10a35f2e591a5015455ee72f7ddb Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Tue, 23 Jun 2026 13:59:48 -0400 Subject: [PATCH 1/4] Prefer inherent methods in trait object method resolution --- compiler/rustc_hir_typeck/src/method/probe.rs | 50 +++++++++++++------ .../methods/call-ambig-trait-object-method.rs | 49 +++++++++++++++--- .../call-ambig-trait-object-method.stderr | 25 ---------- 3 files changed, 76 insertions(+), 48 deletions(-) delete mode 100644 tests/ui/methods/call-ambig-trait-object-method.stderr diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index 4258896deec70..632e78c9328bd 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -62,6 +62,7 @@ pub(crate) struct ProbeContext<'a, 'tcx> { steps: &'tcx [CandidateStep<'tcx>], inherent_candidates: Vec>, + dyn_extension_candidates: Vec>, extension_candidates: Vec>, impl_dups: FxHashSet, @@ -102,6 +103,18 @@ pub(crate) struct Candidate<'tcx> { pub(crate) import_ids: &'tcx [LocalDefId], } +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +enum InherentOrExtension { + /// Inherent candidates + Inherent, + /// Candidates for a trait object's trait & supertraits. + /// Take precedence over other extension candidates, + /// but not inherent candidates + DynExtension, + /// Extension candidates + Extension, +} + #[derive(Debug, Clone)] pub(crate) enum CandidateKind<'tcx> { InherentImplCandidate { impl_def_id: DefId, receiver_steps: usize }, @@ -369,6 +382,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Ok(probe_cx .inherent_candidates .into_iter() + .chain(probe_cx.dyn_extension_candidates) .chain(probe_cx.extension_candidates) .collect()) }, @@ -576,7 +590,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ), import_ids: &[], }, - false, + InherentOrExtension::Extension, ); } }; @@ -772,6 +786,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { method_name, return_type, inherent_candidates: Vec::new(), + dyn_extension_candidates: Vec::new(), extension_candidates: Vec::new(), impl_dups: FxHashSet::default(), orig_steps_var_values, @@ -787,6 +802,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { fn reset(&mut self) { self.inherent_candidates.clear(); + self.dyn_extension_candidates.clear(); self.extension_candidates.clear(); self.impl_dups.clear(); self.private_candidates.clear(); @@ -807,7 +823,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { /////////////////////////////////////////////////////////////////////////// // CANDIDATE ASSEMBLY - fn push_candidate(&mut self, candidate: Candidate<'tcx>, is_inherent: bool) { + fn push_candidate(&mut self, candidate: Candidate<'tcx>, is_inherent: InherentOrExtension) { let is_accessible = if let Some(name) = self.method_name { let item = candidate.item; let hir_id = self.tcx.local_def_id_to_hir_id(self.body_id); @@ -818,11 +834,12 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { true }; if is_accessible { - if is_inherent { - self.inherent_candidates.push(candidate); - } else { - self.extension_candidates.push(candidate); - } + let list_to_push_to = match is_inherent { + InherentOrExtension::Inherent => &mut self.inherent_candidates, + InherentOrExtension::DynExtension => &mut self.dyn_extension_candidates, + InherentOrExtension::Extension => &mut self.extension_candidates, + }; + list_to_push_to.push(candidate); } else { self.private_candidates.push(candidate); } @@ -948,7 +965,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { kind: InherentImplCandidate { impl_def_id, receiver_steps }, import_ids: &[], }, - true, + InherentOrExtension::Inherent, ); } } @@ -980,7 +997,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { |this, new_trait_ref, item| { this.push_candidate( Candidate { item, kind: ObjectCandidate(new_trait_ref), import_ids: &[] }, - true, + InherentOrExtension::DynExtension, ); }, ); @@ -1015,7 +1032,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { self.assemble_candidates_for_bounds(bounds, |this, poly_trait_ref, item| { this.push_candidate( Candidate { item, kind: WhereClauseCandidate(poly_trait_ref), import_ids: &[] }, - true, + InherentOrExtension::Inherent, ); }); } @@ -1115,7 +1132,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { import_ids, kind: TraitCandidate(bound_trait_ref, lint_ambiguous), }, - false, + InherentOrExtension::Extension, ); } } @@ -1138,7 +1155,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { import_ids, kind: TraitCandidate(ty::Binder::dummy(trait_ref), lint_ambiguous), }, - false, + InherentOrExtension::Extension, ); } } @@ -1152,6 +1169,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { let mut names: Vec<_> = self .inherent_candidates .iter() + .chain(&self.dyn_extension_candidates) .chain(&self.extension_candidates) .filter(|candidate| candidate_filter(&candidate.item)) .filter(|candidate| { @@ -1677,9 +1695,11 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { ) -> Option> { debug!("pick_method(self_ty={})", self.ty_to_string(self_ty)); - for (kind, candidates) in - [("inherent", &self.inherent_candidates), ("extension", &self.extension_candidates)] - { + for (kind, candidates) in [ + ("inherent", &self.inherent_candidates), + ("dyn extension", &self.dyn_extension_candidates), + ("extension", &self.extension_candidates), + ] { debug!("searching {} candidates", kind); let res = self.consider_candidates( self_ty, diff --git a/tests/ui/methods/call-ambig-trait-object-method.rs b/tests/ui/methods/call-ambig-trait-object-method.rs index 9e2f5c5aebe8b..a9bc8768eb65c 100644 --- a/tests/ui/methods/call-ambig-trait-object-method.rs +++ b/tests/ui/methods/call-ambig-trait-object-method.rs @@ -1,20 +1,53 @@ -//! Regression test for . -//! Test that name clashes between the method in an impl for the type -//! and the method in the trait when both are in the same scope. +//@ edition: 2024 +//@ run-pass + +//! Regression test for . +//! Test that for trait objects, inherent methods take precedence +//! over the underlying trait implementation, +//! which themselves take precedence over other trait implementations. trait T { - fn foo(&self); + fn foo(&self) -> i32; + fn bar(&self) -> i32; } impl<'a> dyn T + 'a { - fn foo(&self) {} + fn foo(&self) -> i32 { + 1 + } + + fn bar(&self) {} } impl T for i32 { - fn foo(&self) {} + fn foo(&self) -> i32 { + 0 + } + + fn bar(&self) -> i32 { + 0 + } } +trait OtherTrait { + fn foo(&self) -> i32 { + i32::MIN + } +} + +impl OtherTrait for dyn T {} + fn main() { - let x: &dyn T = &0i32; - x.foo(); //~ ERROR multiple applicable items in scope [E0034] + let x = &0i32; + assert_eq!(x.foo(), 0); + assert_eq!(x.bar(), 0); + + let x: &dyn T = x; + assert_eq!(x.foo(), 1); + assert_eq!(x.bar(), ()); + assert_eq!(::foo(x), 1); + assert_eq!(::foo(x), 0); + assert_eq!(::bar(x), ()); + assert_eq!(::bar(x), 0); + assert_eq!(::foo(x), i32::MIN); } diff --git a/tests/ui/methods/call-ambig-trait-object-method.stderr b/tests/ui/methods/call-ambig-trait-object-method.stderr deleted file mode 100644 index b76f107160011..0000000000000 --- a/tests/ui/methods/call-ambig-trait-object-method.stderr +++ /dev/null @@ -1,25 +0,0 @@ -error[E0034]: multiple applicable items in scope - --> $DIR/call-ambig-trait-object-method.rs:19:7 - | -LL | x.foo(); - | ^^^ multiple `foo` found - | -note: candidate #1 is defined in the trait `T` - --> $DIR/call-ambig-trait-object-method.rs:6:5 - | -LL | fn foo(&self); - | ^^^^^^^^^^^^^^ -note: candidate #2 is defined in an impl for the type `(dyn T + 'a)` - --> $DIR/call-ambig-trait-object-method.rs:10:5 - | -LL | fn foo(&self) {} - | ^^^^^^^^^^^^^ -help: disambiguate the method for candidate #1 - | -LL - x.foo(); -LL + T::foo(&x); - | - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0034`. From 4b8693c4c1634ecbebbad9f95ee7dc452c007648 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Tue, 23 Jun 2026 20:03:49 -0400 Subject: [PATCH 2/4] Add `+ Send` test --- tests/ui/methods/call-ambig-trait-object-method.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ui/methods/call-ambig-trait-object-method.rs b/tests/ui/methods/call-ambig-trait-object-method.rs index a9bc8768eb65c..e88c6ecdd07d7 100644 --- a/tests/ui/methods/call-ambig-trait-object-method.rs +++ b/tests/ui/methods/call-ambig-trait-object-method.rs @@ -42,7 +42,7 @@ fn main() { assert_eq!(x.foo(), 0); assert_eq!(x.bar(), 0); - let x: &dyn T = x; + let x: &dyn T = &0i32; assert_eq!(x.foo(), 1); assert_eq!(x.bar(), ()); assert_eq!(::foo(x), 1); @@ -50,4 +50,7 @@ fn main() { assert_eq!(::bar(x), ()); assert_eq!(::bar(x), 0); assert_eq!(::foo(x), i32::MIN); + + let x: &(dyn T + Send) = &0i32; + assert_eq!(x.foo(), 0); } From e7cb8ea419d466e1cdc9d1b8f9cb27dc54d3586f Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 1 Jul 2026 17:04:33 -0400 Subject: [PATCH 3/4] Rename a method --- compiler/rustc_hir_typeck/src/method/probe.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index 632e78c9328bd..6fc180176eaf3 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -880,9 +880,9 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { let (QueryResponse { value: generalized_self_ty, .. }, _ignored_var_values) = self.fcx.instantiate_canonical(self.span, self_ty); - self.assemble_inherent_candidates_from_object(generalized_self_ty); self.assemble_inherent_impl_candidates_for_type(p.def_id(), receiver_steps); self.assemble_inherent_candidates_for_incoherent_ty(raw_self_ty, receiver_steps); + self.assemble_dyn_extension_candidates(generalized_self_ty); } ty::Adt(def, _) => { let def_id = def.did(); @@ -971,7 +971,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { } #[instrument(level = "debug", skip(self))] - fn assemble_inherent_candidates_from_object(&mut self, self_ty: Ty<'tcx>) { + fn assemble_dyn_extension_candidates(&mut self, self_ty: Ty<'tcx>) { let principal = match self_ty.kind() { ty::Dynamic(data, ..) => Some(data), _ => None, From 54d732eaef99900f8f8d4d25f6a2ce6243bac7d0 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 15 Jul 2026 15:05:59 -0400 Subject: [PATCH 4/4] Restrict non-inherency to supertrait methods --- compiler/rustc_hir_typeck/src/method/probe.rs | 9 +- ...ll-ambig-trait-object-method-supertrait.rs | 32 +++++++ .../methods/call-ambig-trait-object-method.rs | 10 +- .../call-ambig-trait-object-method.stderr | 91 +++++++++++++++++++ 4 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 tests/ui/methods/call-ambig-trait-object-method-supertrait.rs create mode 100644 tests/ui/methods/call-ambig-trait-object-method.stderr diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index 6fc180176eaf3..c9eeab98ceae4 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -107,7 +107,8 @@ pub(crate) struct Candidate<'tcx> { enum InherentOrExtension { /// Inherent candidates Inherent, - /// Candidates for a trait object's trait & supertraits. + /// Candidates for a trait object's supertraits + /// (but not the trait itself). /// Take precedence over other extension candidates, /// but not inherent candidates DynExtension, @@ -997,7 +998,11 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { |this, new_trait_ref, item| { this.push_candidate( Candidate { item, kind: ObjectCandidate(new_trait_ref), import_ids: &[] }, - InherentOrExtension::DynExtension, + if new_trait_ref == trait_ref { + InherentOrExtension::Inherent + } else { + InherentOrExtension::DynExtension + }, ); }, ); diff --git a/tests/ui/methods/call-ambig-trait-object-method-supertrait.rs b/tests/ui/methods/call-ambig-trait-object-method-supertrait.rs new file mode 100644 index 0000000000000..a2af5f66ba308 --- /dev/null +++ b/tests/ui/methods/call-ambig-trait-object-method-supertrait.rs @@ -0,0 +1,32 @@ +//@ edition: 2024 +//@ run-pass + +trait Super { + fn foo(&self) -> i32 { + 27 + } +} + +trait Sub: Super {} + +impl<'a> dyn Sub + 'a { + fn foo(&self) -> i32 { + 42 + } +} + +impl Super for i32 {} +impl Sub for i32 {} + +fn main() { + let x = &0i32; + assert_eq!(x.foo(), 27); + + let x: &dyn Sub = &0i32; + assert_eq!(x.foo(), 42); + assert_eq!(::foo(x), 42); + assert_eq!(::foo(x), 27); + + let x: &(dyn Sub + Send) = &0i32; + assert_eq!(x.foo(), 27); +} diff --git a/tests/ui/methods/call-ambig-trait-object-method.rs b/tests/ui/methods/call-ambig-trait-object-method.rs index e88c6ecdd07d7..be80f76ec630d 100644 --- a/tests/ui/methods/call-ambig-trait-object-method.rs +++ b/tests/ui/methods/call-ambig-trait-object-method.rs @@ -1,10 +1,4 @@ //@ edition: 2024 -//@ run-pass - -//! Regression test for . -//! Test that for trait objects, inherent methods take precedence -//! over the underlying trait implementation, -//! which themselves take precedence over other trait implementations. trait T { fn foo(&self) -> i32; @@ -44,10 +38,14 @@ fn main() { let x: &dyn T = &0i32; assert_eq!(x.foo(), 1); + //~^ ERROR multiple applicable items in scope assert_eq!(x.bar(), ()); + //~^ ERROR multiple applicable items in scope assert_eq!(::foo(x), 1); + //~^ ERROR multiple applicable items in scope assert_eq!(::foo(x), 0); assert_eq!(::bar(x), ()); + //~^ ERROR multiple applicable items in scope assert_eq!(::bar(x), 0); assert_eq!(::foo(x), i32::MIN); diff --git a/tests/ui/methods/call-ambig-trait-object-method.stderr b/tests/ui/methods/call-ambig-trait-object-method.stderr new file mode 100644 index 0000000000000..16038fb3c9292 --- /dev/null +++ b/tests/ui/methods/call-ambig-trait-object-method.stderr @@ -0,0 +1,91 @@ +error[E0034]: multiple applicable items in scope + --> $DIR/call-ambig-trait-object-method.rs:40:18 + | +LL | assert_eq!(x.foo(), 1); + | ^^^ multiple `foo` found + | +note: candidate #1 is defined in the trait `T` + --> $DIR/call-ambig-trait-object-method.rs:4:5 + | +LL | fn foo(&self) -> i32; + | ^^^^^^^^^^^^^^^^^^^^^ +note: candidate #2 is defined in an impl for the type `(dyn T + 'a)` + --> $DIR/call-ambig-trait-object-method.rs:9:5 + | +LL | fn foo(&self) -> i32 { + | ^^^^^^^^^^^^^^^^^^^^ +help: disambiguate the method for candidate #1 + | +LL - assert_eq!(x.foo(), 1); +LL + assert_eq!(T::foo(&x), 1); + | + +error[E0034]: multiple applicable items in scope + --> $DIR/call-ambig-trait-object-method.rs:42:18 + | +LL | assert_eq!(x.bar(), ()); + | ^^^ multiple `bar` found + | +note: candidate #1 is defined in the trait `T` + --> $DIR/call-ambig-trait-object-method.rs:5:5 + | +LL | fn bar(&self) -> i32; + | ^^^^^^^^^^^^^^^^^^^^^ +note: candidate #2 is defined in an impl for the type `(dyn T + 'a)` + --> $DIR/call-ambig-trait-object-method.rs:13:5 + | +LL | fn bar(&self) {} + | ^^^^^^^^^^^^^ +help: disambiguate the method for candidate #1 + | +LL - assert_eq!(x.bar(), ()); +LL + assert_eq!(T::bar(&x), ()); + | + +error[E0034]: multiple applicable items in scope + --> $DIR/call-ambig-trait-object-method.rs:44:25 + | +LL | assert_eq!(::foo(x), 1); + | ^^^ multiple `foo` found + | +note: candidate #1 is defined in the trait `T` + --> $DIR/call-ambig-trait-object-method.rs:4:5 + | +LL | fn foo(&self) -> i32; + | ^^^^^^^^^^^^^^^^^^^^^ +note: candidate #2 is defined in an impl for the type `(dyn T + 'a)` + --> $DIR/call-ambig-trait-object-method.rs:9:5 + | +LL | fn foo(&self) -> i32 { + | ^^^^^^^^^^^^^^^^^^^^ +help: use fully-qualified syntax to disambiguate + | +LL - assert_eq!(::foo(x), 1); +LL + assert_eq!(T::foo(x), 1); + | + +error[E0034]: multiple applicable items in scope + --> $DIR/call-ambig-trait-object-method.rs:47:25 + | +LL | assert_eq!(::bar(x), ()); + | ^^^ multiple `bar` found + | +note: candidate #1 is defined in the trait `T` + --> $DIR/call-ambig-trait-object-method.rs:5:5 + | +LL | fn bar(&self) -> i32; + | ^^^^^^^^^^^^^^^^^^^^^ +note: candidate #2 is defined in an impl for the type `(dyn T + 'a)` + --> $DIR/call-ambig-trait-object-method.rs:13:5 + | +LL | fn bar(&self) {} + | ^^^^^^^^^^^^^ +help: use fully-qualified syntax to disambiguate + | +LL - assert_eq!(::bar(x), ()); +LL + assert_eq!(T::bar(x), ()); + | + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0034`.