Skip to content

-Znext-solver Remove a problematic assertion from probing object bound candidates#152859

Open
ShoyuVanilla wants to merge 1 commit intorust-lang:mainfrom
ShoyuVanilla:issue-152789
Open

-Znext-solver Remove a problematic assertion from probing object bound candidates#152859
ShoyuVanilla wants to merge 1 commit intorust-lang:mainfrom
ShoyuVanilla:issue-152789

Conversation

@ShoyuVanilla
Copy link
Member

@ShoyuVanilla ShoyuVanilla commented Feb 19, 2026

Fixes #152789 and fixes #151329

r? lcnr

Yeah, this is the worst fix for such kind of ICEs. But wait, let me make some excuses 😅

Root Cause

pub trait Trait<T> {
    type Assoc;
}

pub trait Foo {
    type FooAssoc;
}

pub struct Wrap<U: Foo>(<dyn Trait<i32, Assoc = i64> as Trait<U::FooAssoc>>::Assoc)
where
    dyn Trait<i32, Assoc = i64>: Trait<U::FooAssoc>;

From the above code, when we try ty prove the well-formedness of <dyn Trait<i32, Assoc = i64> as Trait<U::FooAssoc>>::Assoc, we probe the object bound candidates.

let mut replace_projection_with: HashMap<_, Vec<_>> = HashMap::default();
for bound in object_bounds.iter() {
if let ty::ExistentialPredicate::Projection(proj) = bound.skip_binder() {
// FIXME: We *probably* should replace this with a dummy placeholder,
// b/c don't want to replace literal instances of this dyn type that
// show up in the bounds, but just ones that come from substituting
// `Self` with the dyn type.
let proj = proj.with_self_ty(cx, trait_ref.self_ty());
replace_projection_with.entry(proj.def_id()).or_default().push(bound.rebind(proj));
}
}
let mut folder = ReplaceProjectionWith {
ecx,
param_env,
self_ty: trait_ref.self_ty(),
mapping: &replace_projection_with,
nested: vec![],
};
let requirements = requirements.try_fold_with(&mut folder)?;

We gather the object bounds from the above lines: we have a kv pair (Trait::Assoc, vec![<dyn Trait<i32, Assoc = i64> as Trait<i32>>::Assoc = i64]) for mapping.

And then try to eagerly replace the projection types in our goal = <dyn Trait<i32, Assoc = i64> as Trait<U::FooAssoc>>::Assoc = ?_, i.e. <dyn Trait<i32, Assoc = i64> as Trait<U::FooAssoc>>::Assoc.

/// Try to replace an alias with the term present in the projection bounds of the self type.
/// Returns `Ok<None>` if this alias is not eligible to be replaced, or bail with
/// `Err(Ambiguous)` if it's uncertain which projection bound to replace the term with due
/// to multiple bounds applying.
fn try_eagerly_replace_alias(
&mut self,
alias_term: ty::AliasTerm<I>,
) -> Result<Option<I::Term>, Ambiguous> {
if alias_term.self_ty() != self.self_ty {
return Ok(None);
}
let Some(replacements) = self.mapping.get(&alias_term.def_id) else {
return Ok(None);
};
// This is quite similar to the `projection_may_match` we use in unsizing,
// but here we want to unify a projection predicate against an alias term
// so we can replace it with the projection predicate's term.
let mut matching_projections = replacements
.iter()
.filter(|source_projection| self.projection_may_match(**source_projection, alias_term));
let Some(replacement) = matching_projections.next() else {
// This shouldn't happen.
panic!("could not replace {alias_term:?} with term from from {:?}", self.self_ty);
};

We lookup mapping by the assoc ty id: Trait::Assoc, so we get replacements = vec![<dyn Trait<i32, Assoc = i64> as Trait<i32>>::Assoc = i64].

But dyn Trait<i32, Assoc = i64> as Trait<i32>>::Assoc = i64 cannot match with <dyn Trait<i32, Assoc = i64> as Trait<U::FooAssoc>>::Assoc, panic💥

Actually, that goal should be normalized/proved by the where clause from the param env: dyn Trait<i32, Assoc = i64>: Trait<U::FooAssoc>.
But unfortunately, that has the same assoc ty id with the object's bound so we trigger the assertion, which has no problem when we have different assoc ty ids, instead of giving up the normalization.

In short, the replacements should be None and thus we should short-circuit, but unfortunately we have some confusing one by the same assoc ty id.

Isn't there any alternative?

Well, perhaps we can gather trait predicates from the param env as well into the mappings but this doesn't feel correct because..

  1. We sometimes don't have any useful predicate in param env at all, probably in erroneous cases.
    pub trait Trait<T> {
        type Assoc;
    }
    
    pub trait Foo {
        type FooAssoc;
    }
    
    pub struct Wrap<U: Foo>(<dyn Trait<i32, Assoc = i64> as Trait<U::FooAssoc>>::Assoc);
    We can't avoid ICEing here, instead of just emitting an error.
  2. Sometimes, there exists a valid bound in the param env but it might not be straightforward.
    pub trait Trait<T> {
        type Assoc;
    }
    
    pub trait Trait2<T> {
        type Assoc2;
    }
    
    impl<T, U: ?Sized> Trait<T> for U
    where
        U: Trait2<T>,
    {
        type Assoc = <U as Trait2<T>>::Assoc2;
    }
    
    pub trait Foo {
        type FooAssoc;
    }
    
    pub struct Wrap<U: Foo>(<dyn Trait<i32, Assoc = i64> as Trait<U::FooAssoc>>::Assoc)
    where
        dyn Trait<i32, Assoc = i64>: Trait2<U::FooAssoc>;
    The relationship between where-clauses dyn Trait<i32, Assoc = i64>: Trait2<U::FooAssoc> and dyn Trait<i32, Assoc = i64>: Trait<U::FooAssoc> is not so structural.
  3. After all, the assoc ty ids are equal just coincidentally. Utilizing the predicates outiside the object bounds as if they were object bounds might mess up candidate preferences.

Or perhaps we index mappings by (assoc_ty, args), so don't end up getting replacements = Some(_) when it should be None.
But there might be some cases that the args are not necessarily equal but can be equalized modulo Relate and if so, we have to just try match for all keys with the same assoc ty id, which doesn't make the situation any better

Conclusion

So, I think the simplest fix is just removing the possibly wrong assertion. Yeah, this feels lazy but I have some reasons 😅
After all, the goal can be proven, not with the object bound candidates but with the param env and that's the right path.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver) labels Feb 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

-Znext-solver: trait object candidate ICE "could not replace AliasTerm" [ICE]: could not replace AliasTerm (unsatisifed bounds)

3 participants