Auto merge of #145262 - compiler-errors:prefer-only-param, r=lcnr

Make sure to treat only param where clauses as inherent

See the description in the test file.

This PR fixes a bug introduced by rust-lang/rust#141333, where we considered non-`Param` where clauses to be "inherent" for the purpose of method probing, which leads to both changes in method ambiguity (see test) and also import usage linting (and thus fixes https://github.com/rust-lang/rust/issues/145185).

r? `@lcnr`
This commit is contained in:
bors 2025-08-24 23:52:27 +00:00
commit a1dbb44352
8 changed files with 223 additions and 0 deletions

View file

@ -909,6 +909,10 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
debug_assert_matches!(param_ty.kind(), ty::Param(_));
let tcx = self.tcx;
// We use `DeepRejectCtxt` here which may return false positive on where clauses
// with alias self types. We need to later on reject these as inherent candidates
// in `consider_probe`.
let bounds = self.param_env.caller_bounds().iter().filter_map(|predicate| {
let bound_predicate = predicate.kind();
match bound_predicate.skip_binder() {
@ -1945,6 +1949,29 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
);
(xform_self_ty, xform_ret_ty) =
self.xform_self_ty(probe.item, trait_ref.self_ty(), trait_ref.args);
if matches!(probe.kind, WhereClauseCandidate(_)) {
// `WhereClauseCandidate` requires that the self type is a param,
// because it has special behavior with candidate preference as an
// inherent pick.
match ocx.structurally_normalize_ty(
cause,
self.param_env,
trait_ref.self_ty(),
) {
Ok(ty) => {
if !matches!(ty.kind(), ty::Param(_)) {
debug!("--> not a param ty: {xform_self_ty:?}");
return ProbeResult::NoMatch;
}
}
Err(errors) => {
debug!("--> cannot relate self-types {:?}", errors);
return ProbeResult::NoMatch;
}
}
}
xform_self_ty = ocx.normalize(cause, self.param_env, xform_self_ty);
match ocx.relate(cause, self.param_env, self.variance(), self_ty, xform_self_ty)
{

View file

@ -0,0 +1,17 @@
// Regression test for <github.com/rust-lang/rust/issues/145185>.
mod module {
pub trait Trait {
fn method(&self);
}
}
// Note that we do not import Trait
use std::ops::Deref;
fn foo(x: impl Deref<Target: module::Trait>) {
x.method();
//~^ ERROR no method named `method` found for type parameter
}
fn main() {}

View file

@ -0,0 +1,17 @@
error[E0599]: no method named `method` found for type parameter `impl Deref<Target : module::Trait>` in the current scope
--> $DIR/rigid-alias-bound-is-not-inherent-2.rs:13:7
|
LL | fn foo(x: impl Deref<Target: module::Trait>) {
| --------------------------------- method `method` not found for this type parameter
LL | x.method();
| ^^^^^^ method not found in `impl Deref<Target : module::Trait>`
|
= help: items from traits can only be used if the trait is in scope
help: trait `Trait` which provides `method` is implemented but not in scope; perhaps you want to import it
|
LL + use module::Trait;
|
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0599`.

View file

@ -0,0 +1,26 @@
use std::ops::Deref;
trait Trait1 {
fn call_me(&self) {}
}
impl<T> Trait1 for T {}
trait Trait2 {
fn call_me(&self) {}
}
impl<T> Trait2 for T {}
pub fn foo<T, U>(x: T)
where
T: Deref<Target = U>,
U: Trait1,
{
// This should be ambiguous. The fact that there's an inherent where-bound
// candidate for `U` should not impact the candidates for `T`
x.call_me();
//~^ ERROR multiple applicable items in scope
}
fn main() {}

View file

@ -0,0 +1,30 @@
error[E0034]: multiple applicable items in scope
--> $DIR/rigid-alias-bound-is-not-inherent-3.rs:22:7
|
LL | x.call_me();
| ^^^^^^^ multiple `call_me` found
|
note: candidate #1 is defined in an impl of the trait `Trait1` for the type `T`
--> $DIR/rigid-alias-bound-is-not-inherent-3.rs:4:5
|
LL | fn call_me(&self) {}
| ^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl of the trait `Trait2` for the type `T`
--> $DIR/rigid-alias-bound-is-not-inherent-3.rs:10:5
|
LL | fn call_me(&self) {}
| ^^^^^^^^^^^^^^^^^
help: disambiguate the method for candidate #1
|
LL - x.call_me();
LL + Trait1::call_me(&x);
|
help: disambiguate the method for candidate #2
|
LL - x.call_me();
LL + Trait2::call_me(&x);
|
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0034`.

View file

@ -0,0 +1,30 @@
error[E0034]: multiple applicable items in scope
--> $DIR/rigid-alias-bound-is-not-inherent.rs:42:7
|
LL | x.method();
| ^^^^^^ multiple `method` found
|
note: candidate #1 is defined in the trait `Trait1`
--> $DIR/rigid-alias-bound-is-not-inherent.rs:21:5
|
LL | fn method(&self) {
| ^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl of the trait `Trait2` for the type `T`
--> $DIR/rigid-alias-bound-is-not-inherent.rs:27:5
|
LL | fn method(&self) {
| ^^^^^^^^^^^^^^^^
help: disambiguate the method for candidate #1
|
LL - x.method();
LL + Trait1::method(&x);
|
help: disambiguate the method for candidate #2
|
LL - x.method();
LL + Trait2::method(&x);
|
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0034`.

View file

@ -0,0 +1,30 @@
error[E0034]: multiple applicable items in scope
--> $DIR/rigid-alias-bound-is-not-inherent.rs:42:7
|
LL | x.method();
| ^^^^^^ multiple `method` found
|
note: candidate #1 is defined in the trait `Trait1`
--> $DIR/rigid-alias-bound-is-not-inherent.rs:21:5
|
LL | fn method(&self) {
| ^^^^^^^^^^^^^^^^
note: candidate #2 is defined in the trait `Trait2`
--> $DIR/rigid-alias-bound-is-not-inherent.rs:27:5
|
LL | fn method(&self) {
| ^^^^^^^^^^^^^^^^
help: disambiguate the method for candidate #1
|
LL - x.method();
LL + Trait1::method(&x);
|
help: disambiguate the method for candidate #2
|
LL - x.method();
LL + Trait2::method(&x);
|
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0034`.

View file

@ -0,0 +1,46 @@
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
// See the code below.
//
// We were using `DeepRejectCtxt` to ensure that `assemble_inherent_candidates_from_param`
// did not rely on the param-env being eagerly normalized. Since aliases unify with all
// types, this meant that a rigid param-env candidate like `<T as Deref>::Target: Trait1`
// would be registered as a "WhereClauseCandidate", which is treated as inherent. Since
// we evaluate these candidates for all self types in the deref chain, this candidate
// would be satisfied for `<T as Deref>::Target`, meaning that it would be preferred over
// an "extension" candidate like `<T as Deref>::Target: Trait2` even though it holds.
// This is problematic, since it causes ambiguities to be broken somewhat arbitrarily.
// And as a side-effect, it also caused our computation of "used" traits to be miscalculated
// since inherent candidates don't count as an import usage.
use std::ops::Deref;
trait Trait1 {
fn method(&self) {
println!("1");
}
}
trait Trait2 {
fn method(&self) {
println!("2");
}
}
impl<T: Other + ?Sized> Trait2 for T {}
trait Other {}
fn foo<T>(x: T)
where
T: Deref,
<T as Deref>::Target: Trait1 + Other,
{
// Make sure that we don't prefer methods from where clauses for rigid aliases,
// just for params. We could revisit this behavior, but it would be a lang change.
x.method();
//~^ ERROR multiple applicable items in scope
}
fn main() {}