rust-lang/rust · critical

expected to be able to unify goal projection with dyn's proj

Error message

expected to be able to unify goal projection with dyn's projection

What it means

This `expect` at structural_traits.rs:1029 sits in `try_eagerly_replace_alias`, the machinery that rewrites a projection alias on a `dyn Trait` self type by unifying the goal's alias term against a projection bound drawn from the dyn's principal. The expectation is that once `projection_may_match` succeeded earlier in the function, the inference `eq_and_get_goals` must succeed. Failure means the projection bound that passed the cheap `may_match` structural check still failed full unification — an internal inconsistency in the next-solver's dyn coercion path.

Source

Thrown at compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs:1029

            .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);
        };
        // FIXME: This *may* have issues with duplicated projections.
        if matching_projections.next().is_some() {
            // If there's more than one projection that we can unify here, then we
            // need to stall until inference constrains things so that there's only
            // one choice.
            return Err(Ambiguous);
        }

        let replacement = self.ecx.instantiate_binder_with_infer(*replacement);
        self.nested.extend(
            self.ecx
                .eq_and_get_goals(self.param_env, alias_term, replacement.projection_term)
                .expect("expected to be able to unify goal projection with dyn's projection"),
        );

        Ok(Some(replacement.term))
    }
}

/// Marker for bailing with ambiguity.
pub(crate) struct Ambiguous;

impl<D, I> FallibleTypeFolder<I> for ReplaceProjectionWith<'_, '_, I, D>
where
    D: SolverDelegate<Interner = I>,
    I: Interner,
{
    type Error = Ambiguous;

    fn cx(&self) -> I {
        self.ecx.cx()

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Confirm whether `-Znext-solver` is on; the panic is specific to that solver path, so removing the flag avoids it.
  2. Reduce the dyn coercion / associated-type bound to a minimal case and report at https://github.com/rust-lang/rust (label next-solver).
  3. Bisect the nightly toolchain (`cargo bisect-rustc`) to identify the regressing commit.
  4. Temporarily replace the `dyn Trait<Assoc=…>` usage with a concrete type or a where-clause bound to sidestep the projection replacement.

Example fix

// before: dyn with associated-type projection that next-solver cannot unify
fn coerce<'a>(x: &'a dyn Trait<Assoc = u32>) -> &'a dyn Trait { x }

// after: avoid projection replacement via explicit where-clause
fn coerce<'a, T: ?Sized + Trait<Assoc = u32> + 'a>(x: &'a T) -> &'a dyn Trait where 'a: 'a { x }
Defensive patterns

Strategy: type-guard

Validate before calling

// Before projecting an associated item through a value, ensure it isn't an opaque `dyn`.
fn concrete_not_dyn<T: ?Sized>(_v: &T) where T: Sized {} // caller passes concrete types only

Type guard

// Guard: only allow projections on concrete types; reject `dyn Trait` at the call site.
fn is_dyn_trait_object<T: ?Sized>() -> bool {
    std::mem::size_of::<*mut T>() == std::mem::size_of::<usize>()
        && !std::mem::size_of::<T>().is_power_of_two_on_concrete()
}
// Prefer an explicit associated-type bound so projection is decidable:
// fn use_it<T: Trait<Assoc = Concrete>>(x: &T) { let _ = x.assoc(); }

Prevention

When it happens

Trigger: Reached when the next trait solver (`-Znext-solver`) evaluates a `dyn Trait + Associated<Type>` whose projection bound structurally matches a goal projection but cannot be equated under the param-env, e.g. during unsizing/coercion of a dyn with associated-type bounds or when relating two dyn types.

Common situations: Nightly with `-Znext-solver` enabled; using `dyn Trait<Assoc = T>` with generic associated types; combining `feature(associated_type_defaults)` or `feature(min_specialization)` with dyn coercions; bugs introduced by a next-solver refactor.

Related errors


AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03). Data as JSON: /data/errors/a5e9e007c8b4888e.json. Report an issue: GitHub.