diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index 3ca3b56b87e01..c857e6b761a86 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -1945,6 +1945,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) { diff --git a/tests/ui/methods/rigid-alias-bound-is-not-inherent.rs b/tests/ui/methods/rigid-alias-bound-is-not-inherent.rs new file mode 100644 index 0000000000000..d721f874b5654 --- /dev/null +++ b/tests/ui/methods/rigid-alias-bound-is-not-inherent.rs @@ -0,0 +1,42 @@ +// 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 `::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 `::Target`, meaning that it would be preferred over +// an "extension" candidate like `::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 Trait2 for T {} + +trait Other {} + +fn foo(x: T) +where + T: 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() {} diff --git a/tests/ui/methods/rigid-alias-bound-is-not-inherent.stderr b/tests/ui/methods/rigid-alias-bound-is-not-inherent.stderr new file mode 100644 index 0000000000000..2b94cfb9c856b --- /dev/null +++ b/tests/ui/methods/rigid-alias-bound-is-not-inherent.stderr @@ -0,0 +1,30 @@ +error[E0034]: multiple applicable items in scope + --> $DIR/rigid-alias-bound-is-not-inherent.rs:38:7 + | +LL | x.method(); + | ^^^^^^ multiple `method` found + | +note: candidate #1 is defined in the trait `Trait1` + --> $DIR/rigid-alias-bound-is-not-inherent.rs:17: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:23: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`.