incompletely prefer opaque type bounds when self type bottoms out in infer

This commit is contained in:
Michael Goulet 2025-04-28 18:16:34 +00:00 committed by lcnr
parent 0e29865434
commit cf224ea1fb
18 changed files with 457 additions and 14 deletions

View file

@ -651,7 +651,11 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
| ty::Bound(_, _) => bug!("unexpected self type: {self_ty}"),
}
let trait_impls = tcx.trait_impls_of(trait_def_id);
#[allow(rustc::usage_of_type_ir_traits)]
self.for_each_blanket_impl(trait_def_id, f)
}
fn for_each_blanket_impl(self, trait_def_id: DefId, mut f: impl FnMut(DefId)) {
let trait_impls = self.trait_impls_of(trait_def_id);
for &impl_def_id in trait_impls.blanket_impls() {
f(impl_def_id);
}

View file

@ -11,8 +11,9 @@ use rustc_type_ir::lang_items::SolverTraitLangItem;
use rustc_type_ir::search_graph::CandidateHeadUsages;
use rustc_type_ir::solve::SizedTraitKind;
use rustc_type_ir::{
self as ty, Interner, TypeFlags, TypeFoldable, TypeSuperVisitable, TypeVisitable,
TypeVisitableExt as _, TypeVisitor, TypingMode, Upcast as _, elaborate,
self as ty, Interner, TypeFlags, TypeFoldable, TypeFolder, TypeSuperFoldable,
TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, Upcast,
elaborate,
};
use tracing::{debug, instrument};
@ -187,6 +188,7 @@ where
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
impl_def_id: I::ImplId,
then: impl FnOnce(&mut EvalCtxt<'_, D>, Certainty) -> QueryResult<I>,
) -> Result<Candidate<I>, NoSolution>;
/// If the predicate contained an error, we want to avoid emitting unnecessary trait
@ -365,6 +367,15 @@ pub(super) enum AssembleCandidatesFrom {
EnvAndBounds,
}
impl AssembleCandidatesFrom {
fn should_assemble_impl_candidates(&self) -> bool {
match self {
AssembleCandidatesFrom::All => true,
AssembleCandidatesFrom::EnvAndBounds => false,
}
}
}
/// This is currently used to track the [CandidateHeadUsages] of all failed `ParamEnv`
/// candidates. This is then used to ignore their head usages in case there's another
/// always applicable `ParamEnv` candidate. Look at how `param_env_head_usages` is
@ -397,14 +408,15 @@ where
return (candidates, failed_candidate_info);
};
let goal: Goal<I, G> = goal
.with(self.cx(), goal.predicate.with_replaced_self_ty(self.cx(), normalized_self_ty));
if normalized_self_ty.is_ty_var() {
debug!("self type has been normalized to infer");
candidates.extend(self.forced_ambiguity(MaybeCause::Ambiguity));
self.try_assemble_bounds_via_registered_opaques(goal, assemble_from, &mut candidates);
return (candidates, failed_candidate_info);
}
let goal: Goal<I, G> = goal
.with(self.cx(), goal.predicate.with_replaced_self_ty(self.cx(), normalized_self_ty));
// Vars that show up in the rest of the goal substs may have been constrained by
// normalizing the self type as well, since type variables are not uniquified.
let goal = self.resolve_vars_if_possible(goal);
@ -484,8 +496,9 @@ where
if cx.impl_is_default(impl_def_id) {
return;
}
match G::consider_impl_candidate(self, goal, impl_def_id) {
match G::consider_impl_candidate(self, goal, impl_def_id, |ecx, certainty| {
ecx.evaluate_added_goals_and_make_canonical_response(certainty)
}) {
Ok(candidate) => candidates.push(candidate),
Err(NoSolution) => (),
}
@ -943,6 +956,116 @@ where
}
}
/// If the self type is the hidden type of an opaque, try to assemble
/// candidates for it by consider its item bounds and by using blanket
/// impls. This is used to incompletely guide type inference when handling
/// non-defining uses in the defining scope.
///
/// We otherwise just fail fail with ambiguity. Even if we're using an
/// opaque type item bound or a blank impls, we still force its certainty
/// to be `Maybe` so that we properly prove this goal later.
///
/// See <https://github.com/rust-lang/trait-system-refactor-initiative/issues/182>
/// for why this is necessary.
fn try_assemble_bounds_via_registered_opaques<G: GoalKind<D>>(
&mut self,
goal: Goal<I, G>,
assemble_from: AssembleCandidatesFrom,
candidates: &mut Vec<Candidate<I>>,
) {
let self_ty = goal.predicate.self_ty();
// If the self type is sub unified with any opaque type, we
// also look at blanket impls for it.
let mut assemble_blanket_impls = false;
for alias_ty in self.opaques_with_sub_unified_hidden_type(self_ty) {
assemble_blanket_impls = true;
debug!("self ty is sub unified with {alias_ty:?}");
struct ReplaceOpaque<I: Interner> {
cx: I,
alias_ty: ty::AliasTy<I>,
self_ty: I::Ty,
}
impl<I: Interner> TypeFolder<I> for ReplaceOpaque<I> {
fn cx(&self) -> I {
self.cx
}
fn fold_ty(&mut self, ty: I::Ty) -> I::Ty {
if let ty::Alias(ty::Opaque, alias_ty) = ty.kind() {
if alias_ty == self.alias_ty {
return self.self_ty;
}
}
ty.super_fold_with(self)
}
}
// We look at all item-bounds of the opaque, replacing the
// opaque with the current self type before considering
// them as a candidate. Imagine e've got `?x: Trait<?y>`
// and `?x` has been sub-unified with the hidden type of
// `impl Trait<u32>`, We take the item bound `opaque: Trait<u32>`
// and replace all occurrences of `opaque` with `?x`. This results
// in a `?x: Trait<u32>` alias-bound candidate.
for item_bound in self
.cx()
.item_self_bounds(alias_ty.def_id)
.iter_instantiated(self.cx(), alias_ty.args)
{
let assumption =
item_bound.fold_with(&mut ReplaceOpaque { cx: self.cx(), alias_ty, self_ty });
candidates.extend(G::probe_and_match_goal_against_assumption(
self,
CandidateSource::AliasBound,
goal,
assumption,
|ecx| {
// We want to reprove this goal once we've inferred the
// hidden type, so we force the certainty to `Maybe`.
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
},
));
}
}
// We also need to consider blanket impls for not-yet-defined opaque types.
//
// See tests/ui/impl-trait/non-defining-uses/use-blanket-impl.rs for an example.
if assemble_blanket_impls && assemble_from.should_assemble_impl_candidates() {
let cx = self.cx();
cx.for_each_blanket_impl(goal.predicate.trait_def_id(cx), |impl_def_id| {
// For every `default impl`, there's always a non-default `impl`
// that will *also* apply. There's no reason to register a candidate
// for this impl, since it is *not* proof that the trait goal holds.
if cx.impl_is_default(impl_def_id) {
return;
}
match G::consider_impl_candidate(self, goal, impl_def_id, |ecx, certainty| {
if ecx.shallow_resolve(self_ty).is_ty_var() {
// We force the certainty of impl candidates to be `Maybe`.
let certainty = certainty.and(Certainty::AMBIGUOUS);
ecx.evaluate_added_goals_and_make_canonical_response(certainty)
} else {
// We don't want to use impls if they constrain the opaque.
//
// FIXME(trait-system-refactor-initiative#229): This isn't
// perfect yet as it still allows us to incorrectly constrain
// other inference variables.
Err(NoSolution)
}
}) {
Ok(candidate) => candidates.push(candidate),
Err(NoSolution) => (),
}
});
}
if candidates.is_empty() {
candidates.extend(self.forced_ambiguity(MaybeCause::Ambiguity));
}
}
/// Assemble and merge candidates for goals which are related to an underlying trait
/// goal. Right now, this is normalizes-to and host effect goals.
///

View file

@ -124,6 +124,7 @@ where
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
impl_def_id: I::ImplId,
then: impl FnOnce(&mut EvalCtxt<'_, D>, Certainty) -> QueryResult<I>,
) -> Result<Candidate<I>, NoSolution> {
let cx = ecx.cx();
@ -175,7 +176,7 @@ where
});
ecx.add_goals(GoalSource::ImplWhereBound, const_conditions);
ecx.evaluate_added_goals_and_make_canonical_response(certainty)
then(ecx, certainty)
})
}

View file

@ -1060,6 +1060,10 @@ where
self.delegate.resolve_vars_if_possible(value)
}
pub(super) fn shallow_resolve(&self, ty: I::Ty) -> I::Ty {
self.delegate.shallow_resolve(ty)
}
pub(super) fn eager_resolve_region(&self, r: I::Region) -> I::Region {
if let ty::ReVar(vid) = r.kind() {
self.delegate.opportunistic_resolve_lt_var(vid)
@ -1176,6 +1180,33 @@ where
) -> bool {
may_use_unstable_feature(&**self.delegate, param_env, symbol)
}
pub(crate) fn opaques_with_sub_unified_hidden_type(
&self,
self_ty: I::Ty,
) -> impl Iterator<Item = ty::AliasTy<I>> + use<'a, D, I> {
let delegate = self.delegate;
delegate
.clone_opaque_types_lookup_table()
.into_iter()
.chain(delegate.clone_duplicate_opaque_types())
.filter_map(move |(key, hidden_ty)| {
if let ty::Infer(ty::TyVar(self_vid)) = self_ty.kind() {
if let ty::Infer(ty::TyVar(hidden_vid)) = hidden_ty.kind() {
if delegate.sub_unification_table_root_var(self_vid)
== delegate.sub_unification_table_root_var(hidden_vid)
{
return Some(ty::AliasTy::new_from_args(
delegate.cx(),
key.def_id.into(),
key.args,
));
}
}
}
None
})
}
}
/// Eagerly replace aliases with inference variables, emitting `AliasRelate`

View file

@ -194,6 +194,7 @@ where
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, NormalizesTo<I>>,
impl_def_id: I::ImplId,
then: impl FnOnce(&mut EvalCtxt<'_, D>, Certainty) -> QueryResult<I>,
) -> Result<Candidate<I>, NoSolution> {
let cx = ecx.cx();
@ -314,8 +315,7 @@ where
// nested goal for consistency.
ty::TypingMode::Coherence => {
ecx.add_goal(GoalSource::Misc, goal.with(cx, PredicateKind::Ambiguous));
return ecx
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
return then(ecx, Certainty::Yes);
}
ty::TypingMode::Analysis { .. }
| ty::TypingMode::Borrowck { .. }
@ -325,8 +325,7 @@ where
goal,
goal.predicate.alias,
);
return ecx
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
return then(ecx, Certainty::Yes);
}
}
} else {

View file

@ -55,6 +55,7 @@ where
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, TraitPredicate<I>>,
impl_def_id: I::ImplId,
then: impl FnOnce(&mut EvalCtxt<'_, D>, Certainty) -> QueryResult<I>,
) -> Result<Candidate<I>, NoSolution> {
let cx = ecx.cx();
@ -112,7 +113,7 @@ where
.map(|pred| goal.with(cx, pred)),
);
ecx.evaluate_added_goals_and_make_canonical_response(maximal_certainty)
then(ecx, maximal_certainty)
})
}

View file

@ -347,6 +347,7 @@ pub trait Interner:
self_ty: Self::Ty,
f: impl FnMut(Self::ImplId),
);
fn for_each_blanket_impl(self, trait_def_id: Self::TraitId, f: impl FnMut(Self::ImplId));
fn has_item_definition(self, def_id: Self::DefId) -> bool;

View file

@ -0,0 +1,16 @@
error[E0308]: mismatched types
--> $DIR/avoid-inference-constraints-from-blanket-2.rs:27:18
|
LL | let _: u32 = x;
| --- ^ expected `u32`, found `u64`
| |
| expected due to this
|
help: you can convert a `u64` to a `u32` and panic if the converted value doesn't fit
|
LL | let _: u32 = x.try_into().unwrap();
| ++++++++++++++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.

View file

@ -0,0 +1,31 @@
//@ revisions: current next
//@[next] compile-flags: -Znext-solver
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[current] check-pass
// Regression test for trait-system-refactor-initiative#205. Avoid
// constraining other impl arguments when applying blanket impls.
// FIXME(-Znext-solver): This currently incompletely constrains the
// argument of `opaque: Trait<?x>` using the blanket impl of trait.
// Ideally we don't do that.
trait Trait<T> {}
impl<T> Trait<u64> for T {}
impl Trait<u32> for u64 {}
fn impls_trait<T: Trait<U>, U>(_: U) -> T {
todo!()
}
fn foo() -> impl Sized {
let x = Default::default();
if false {
return impls_trait::<_, _>(x);
}
let _: u32 = x;
//[next]~^ ERROR mismatched types
1u64
}
fn main() {}

View file

@ -0,0 +1,25 @@
//@ compile-flags: -Znext-solver
//@ ignore-compare-mode-next-solver (explicit revisions)
#![allow(unconditional_recursion)]
// Regression test for trait-system-refactor-initiative#205. Avoid
// constraining other impl arguments when applying blanket impls,
// especially if the nested where-bounds of the blanket impl don't
// actually apply for the opaque.
// FIXME(-Znext-solver): This currently incompletely constrains the
// argument of `opaque: Trait<?x>` using the blanket impl of trait.
// Ideally we don't do that.
trait Trait<T> {}
impl<T: Copy> Trait<u32> for T {}
impl Trait<u64> for String {}
fn impls_trait<T: Trait<U>, U>(_: T) {}
fn test() -> impl Sized {
let x = test();
impls_trait(x); //~ ERROR the trait bound `String: Trait<u32>` is not satisfied
String::new()
}
fn main() {}

View file

@ -0,0 +1,25 @@
error[E0277]: the trait bound `String: Trait<u32>` is not satisfied
--> $DIR/avoid-inference-constraints-from-blanket-3.rs:22:5
|
LL | impls_trait(x);
| ^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `String`
|
= help: the trait `Trait<u32>` is not implemented for `String`
but trait `Trait<u64>` is implemented for it
= help: for that trait implementation, expected `u64`, found `u32`
note: required for `String` to implement `Trait<u32>`
--> $DIR/avoid-inference-constraints-from-blanket-3.rs:16:15
|
LL | impl<T: Copy> Trait<u32> for T {}
| ---- ^^^^^^^^^^ ^
| |
| unsatisfied trait bound introduced here
note: required by a bound in `impls_trait`
--> $DIR/avoid-inference-constraints-from-blanket-3.rs:18:19
|
LL | fn impls_trait<T: Trait<U>, U>(_: T) {}
| ^^^^^^^^ required by this bound in `impls_trait`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0277`.

View file

@ -0,0 +1,25 @@
//@ revisions: current next
//@[next] compile-flags: -Znext-solver
//@ ignore-compare-mode-next-solver (explicit revisions)
//@ check-pass
// Regression test for trait-system-refactor-initiative#205. Avoid constraining
// the opaque type when applying blanket impls.
trait Trait<T> {}
impl<T> Trait<T> for T {}
impl Trait<u32> for u64 {}
fn impls_trait<T: Trait<U>, U>() -> T {
todo!()
}
fn foo() -> impl Sized {
if false {
// `opaque: Trait<u32>` shouldn't constrain `opaque` to `u32` via the blanket impl
return impls_trait::<_, u32>();
}
1u64
}
fn main() {}

View file

@ -0,0 +1,23 @@
//@ compile-flags: -Znext-solver
//@ ignore-compare-mode-next-solver (explicit revisions)
//@ check-pass
#![allow(unconditional_recursion)]
// Regression test for trait-system-refactor-initiative#182. If multiple
// opaque types result in different item bounds, do not apply them.
trait Trait<T> {}
impl<T, U> Trait<T> for U {}
fn impls_trait<T: Trait<U>, U>(_: T) -> U {
todo!()
}
fn overlap<T, U>() -> (impl Trait<T>, impl Trait<U>) {
let mut x = overlap::<T, U>().0;
x = overlap::<T, U>().1;
let u = impls_trait(x);
let _: u32 = u;
((), ())
}
fn main() {}

View file

@ -0,0 +1,37 @@
//@ compile-flags: -Znext-solver
//@ ignore-compare-mode-next-solver (explicit revisions)
//@ check-pass
#![allow(unconditional_recursion)]
// Regression test for trait-system-refactor-initiative#18, making sure
// we support being sub unified with more than 1 opaque type.
trait Id {
type This;
}
impl Id for &'static str {
type This = &'static str;
}
fn to_assoc<T: Id>(x: T) -> <T as Id>::This {
todo!()
}
fn mirror1() -> (impl Id<This = &'static str>, impl Sized) {
let mut opaque = mirror1().0;
opaque = mirror1().1;
let x = to_assoc(opaque);
// `?x` equals both opaques, make sure we still use the applicable
// item bound.
x.len();
(x, x)
}
fn mirror2() -> (impl Sized, impl Id<This = &'static str>) {
let mut opaque = mirror2().0;
opaque = mirror2().1;
let x = to_assoc(opaque);
// `?x` equals both opaques, make sure we still use the applicable
// item bound.
x.len();
(x, x)
}
fn main() {}

View file

@ -0,0 +1,27 @@
//@ compile-flags: -Znext-solver
//@ ignore-compare-mode-next-solver (explicit revisions)
//@ check-pass
#![allow(unconditional_recursion)]
// Test for trait-system-refactor-initiative#182 making sure
// that we don't incorrectly normalize to rigid aliases if the
// opaque type only has a trait bound.
trait Id {
type This;
}
impl<T> Id for Vec<T> {
type This = Vec<T>;
}
fn to_assoc<T: Id>(x: T) -> <T as Id>::This {
todo!()
}
fn mirror<T>(x: Vec<T>) -> impl Id {
let x = to_assoc(mirror(x));
// `?x` equals `<opaque::<T> as Id>::This`. We should not infer `?x`
// to be a rigid alias here.
let _: Vec<u32> = x;
x
}
fn main() {}

View file

@ -0,0 +1,19 @@
//@ revisions: current next
//@[next] compile-flags: -Znext-solver
//@ ignore-compare-mode-next-solver (explicit revisions)
//@ check-pass
// Regression test for trait-system-refactor-initiative#196.
fn iterator(b: bool) -> impl Iterator<Item = String> {
if b {
// We need to eagerly figure out the type of `i` here by using
// the `<opaque as IntoIterator>::Item` obligation. This means
// we not only have to consider item bounds, but also blanket impls.
for i in iterator(false) {
i.len();
}
}
vec![].into_iter()
}
fn main() {}

View file

@ -0,0 +1,30 @@
//@ revisions: current next
//@[next] compile-flags: -Znext-solver
//@ ignore-compare-mode-next-solver (explicit revisions)
//@ check-pass
#![allow(unconditional_recursion)]
// Regression test for trait-system-refactor-initiative#182.
trait Id {
type This;
}
impl<T> Id for T {
type This = T;
}
fn to_assoc<T>(x: T) -> <T as Id>::This {
x
}
fn mirror<T>(x: Vec<T>) -> impl Id<This = Vec<T>> {
let x = to_assoc(mirror(x));
// `?x` equals `<opaque::<T> as Id>::This`. We need to eagerly infer the
// type of `?x` to prevent this method call from resulting in an error.
//
// We could use both the item bound to normalize to `Vec<T>`, or the
// blanket impl to normalize to `opaque::<T>`. We have to go with the
// item bound.
x.len();
x
}
fn main() {}

View file

@ -0,0 +1,25 @@
//@ revisions: current next
//@[next] compile-flags: -Znext-solver
//@ ignore-compare-mode-next-solver (explicit revisions)
//@ check-pass
#![allow(unconditional_recursion)]
// Regression test for trait-system-refactor-initiative#182.
trait Id {
type This;
}
impl<T> Id for Vec<T> {
type This = Vec<T>;
}
fn to_assoc<T: Id>(x: T) -> <T as Id>::This {
todo!()
}
fn mirror<T>(x: Vec<T>) -> impl Id<This = Vec<T>> {
let x = to_assoc(mirror(x));
// `?x` equals `<opaque::<T> as Id>::This`. We need to eagerly infer the
// type of `?x` to prevent this method call from resulting in an error.
x.len();
x
}
fn main() {}