diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs index 57c36a0030aae..4ecd56dfa40b6 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -1125,11 +1125,12 @@ where /// treat the alias as rigid. /// /// See trait-system-refactor-initiative#124 for more details. - #[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)] + #[instrument(level = "debug", skip_all, fields(proven_via, goal), ret)] pub(super) fn assemble_and_merge_candidates>( &mut self, proven_via: Option, goal: Goal, + inject_forced_ambiguity_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> Option>, inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult, ) -> QueryResult { let Some(proven_via) = proven_via else { @@ -1149,15 +1150,24 @@ where // `tests/ui/next-solver/alias-bound-shadowed-by-env.rs`. let (mut candidates, _) = self .assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::EnvAndBounds); + debug!(?candidates); + + // If the trait goal has been proven by using the environment, we want to treat + // aliases as rigid if there are no applicable projection bounds in the environment. + if candidates.is_empty() { + return inject_normalize_to_rigid_candidate(self); + } + + // If we're normalizing an GAT, we bail if using a where-bound would constrain + // its generic arguments. + if let Some(result) = inject_forced_ambiguity_candidate(self) { + return result; + } // We still need to prefer where-bounds over alias-bounds however. // See `tests/ui/winnowing/norm-where-bound-gt-alias-bound.rs`. if candidates.iter().any(|c| matches!(c.source, CandidateSource::ParamEnv(_))) { candidates.retain(|c| matches!(c.source, CandidateSource::ParamEnv(_))); - } else if candidates.is_empty() { - // If the trait goal has been proven by using the environment, we want to treat - // aliases as rigid if there are no applicable projection bounds in the environment. - return inject_normalize_to_rigid_candidate(self); } if let Some((response, _)) = self.try_merge_candidates(&candidates) { diff --git a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs index 2036129d54e44..2cb79a0219f6e 100644 --- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs @@ -446,6 +446,6 @@ where goal.with(ecx.cx(), goal.predicate.trait_ref); ecx.compute_trait_goal(trait_goal) })?; - self.assemble_and_merge_candidates(proven_via, goal, |_ecx| Err(NoSolution)) + self.assemble_and_merge_candidates(proven_via, goal, |_ecx| None, |_ecx| Err(NoSolution)) } } diff --git a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs index 8da4289bf185e..794bda7726a4a 100644 --- a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs @@ -39,15 +39,49 @@ where let trait_goal: Goal> = goal.with(cx, trait_ref); ecx.compute_trait_goal(trait_goal) })?; - self.assemble_and_merge_candidates(proven_via, goal, |ecx| { - ecx.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| { - this.structurally_instantiate_normalizes_to_term( - goal, - goal.predicate.alias, - ); - this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - }) - }) + self.assemble_and_merge_candidates( + proven_via, + goal, + |ecx| { + // FIXME(generic_associated_types): Addresses aggressive inference in #92917. + // + // If this type is a GAT with currently unconstrained arguments, we do not + // want to normalize it via a candidate which only applies for a specific + // instantiation. We could otherwise keep the GAT as rigid and succeed this way. + // See tests/ui/generic-associated-types/no-incomplete-gat-arg-inference.rs. + // + // This only avoids normalization if a GAT argument is fully unconstrained. + // This is quite arbitrary but fixing it causes some ambiguity, see #125196. + for arg in goal.predicate.alias.own_args(cx).iter() { + let Some(term) = arg.as_term() else { + continue; + }; + match ecx.structurally_normalize_term(goal.param_env, term) { + Ok(term) => { + if term.is_infer() { + return Some( + ecx.evaluate_added_goals_and_make_canonical_response( + Certainty::AMBIGUOUS, + ), + ); + } + } + Err(NoSolution) => return Some(Err(NoSolution)), + } + } + + None + }, + |ecx| { + ecx.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| { + this.structurally_instantiate_normalizes_to_term( + goal, + goal.predicate.alias, + ); + this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }) + }, + ) } ty::AliasTermKind::InherentTy | ty::AliasTermKind::InherentConst => { self.normalize_inherent_associated_term(goal) @@ -132,39 +166,7 @@ where then: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult, ) -> QueryResult { let cx = ecx.cx(); - // FIXME(generic_associated_types): Addresses aggressive inference in #92917. - // - // If this type is a GAT with currently unconstrained arguments, we do not - // want to normalize it via a candidate which only applies for a specific - // instantiation. We could otherwise keep the GAT as rigid and succeed this way. - // See tests/ui/generic-associated-types/no-incomplete-gat-arg-inference.rs. - // - // This only avoids normalization if the GAT arguments are fully unconstrained. - // This is quite arbitrary but fixing it causes some ambiguity, see #125196. - match goal.predicate.alias.kind(cx) { - ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => { - for arg in goal.predicate.alias.own_args(cx).iter() { - let Some(term) = arg.as_term() else { - continue; - }; - let term = ecx.structurally_normalize_term(goal.param_env, term)?; - if term.is_infer() { - return ecx.evaluate_added_goals_and_make_canonical_response( - Certainty::AMBIGUOUS, - ); - } - } - } - ty::AliasTermKind::OpaqueTy - | ty::AliasTermKind::InherentTy - | ty::AliasTermKind::InherentConst - | ty::AliasTermKind::FreeTy - | ty::AliasTermKind::FreeConst - | ty::AliasTermKind::UnevaluatedConst => {} - } - let projection_pred = assumption.as_projection_clause().unwrap(); - let assumption_projection_pred = ecx.instantiate_binder_with_infer(projection_pred); ecx.eq(goal.param_env, goal.predicate.alias, assumption_projection_pred.projection_term)?; diff --git a/tests/ui/generic-associated-types/gat-with-ambig-args-still-rigid.rs b/tests/ui/generic-associated-types/gat-with-ambig-args-still-rigid.rs new file mode 100644 index 0000000000000..e3be46bddf225 --- /dev/null +++ b/tests/ui/generic-associated-types/gat-with-ambig-args-still-rigid.rs @@ -0,0 +1,45 @@ +//@ check-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver + +// Regression test for trait-system-refactor-initiative#256. The ambiguous +// GAT arg check previously happened before checking whether the projection +// candidate actually applied in the new trait solver. +// +// This meant we didn't consider `T::Assoc<_>` to be a rigid alias, resulting +// in an inference failure. + +pub trait Proj { + type Assoc; +} + +trait Id { + type This; +} +impl Id for T { + type This = T; +} + +// This previously compiled as the "assumption would incompletely constrain GAT args" +// check happened in each individual assumption after the `DeepRejectCtxt` fast path. +fn with_fast_reject(x: T::Assoc) +where + T: Proj, + U: Proj = u32>, +{ + let _: T::Assoc<_> = x; +} + +// This previously failed with ambiguity as that check did happen before we actually +// equated the goal with the assumption. Due to the alias in the where-clause +// we didn't fast-reject this candidate. +fn no_fast_reject(x: T::Assoc) +where + T: Proj, + ::This: Proj = u32>, +{ + let _: T::Assoc<_> = x; +} + +fn main() {}