handle specialization in the new trait solver

uwu :3
This commit is contained in:
lcnr
2025-04-25 17:59:33 +00:00
parent 8f43b85954
commit 009db53e49
20 changed files with 290 additions and 51 deletions

View File

@@ -10,6 +10,7 @@ use rustc_type_ir::{
};
use tracing::{debug, instrument};
use super::has_only_region_constraints;
use super::trait_goals::TraitGoalProvenVia;
use crate::delegate::SolverDelegate;
use crate::solve::inspect::ProbeKind;
@@ -771,6 +772,69 @@ where
}
})
}
}
pub(super) enum AllowInferenceConstraints {
Yes,
No,
}
impl<D, I> EvalCtxt<'_, D>
where
D: SolverDelegate<Interner = I>,
I: Interner,
{
/// Check whether we can ignore impl candidates due to specialization.
///
/// This is only necessary for `feature(specialization)` and seems quite ugly.
pub(super) fn filter_specialized_impls(
&mut self,
allow_inference_constraints: AllowInferenceConstraints,
candidates: &mut Vec<Candidate<I>>,
) {
match self.typing_mode() {
TypingMode::Coherence => return,
TypingMode::Analysis { .. }
| TypingMode::Borrowck { .. }
| TypingMode::PostBorrowckAnalysis { .. }
| TypingMode::PostAnalysis => {}
}
let mut i = 0;
'outer: while i < candidates.len() {
let CandidateSource::Impl(victim_def_id) = candidates[i].source else {
i += 1;
continue;
};
for (j, c) in candidates.iter().enumerate() {
if i == j {
continue;
}
let CandidateSource::Impl(other_def_id) = c.source else {
continue;
};
// See if we can toss out `victim` based on specialization.
//
// While this requires us to know *for sure* that the `lhs` impl applies
// we still use modulo regions here. This is fine as specialization currently
// assumes that specializing impls have to be always applicable, meaning that
// the only allowed region constraints may be constraints also present on the default impl.
if matches!(allow_inference_constraints, AllowInferenceConstraints::Yes)
|| has_only_region_constraints(c.result)
{
if self.cx().impl_specializes(other_def_id, victim_def_id) {
candidates.remove(i);
continue 'outer;
}
}
}
i += 1;
}
}
/// Assemble and merge candidates for goals which are related to an underlying trait
/// goal. Right now, this is normalizes-to and host effect goals.
@@ -857,7 +921,7 @@ where
}
}
TraitGoalProvenVia::Misc => {
let candidates =
let mut candidates =
self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All);
// Prefer "orphaned" param-env normalization predicates, which are used
@@ -871,6 +935,13 @@ where
return Ok(response);
}
// We drop specialized impls to allow normalization via a final impl here. In case
// the specializing impl has different inference constraints from the specialized
// impl, proving the trait goal is already ambiguous, so we never get here. This
// means we can just ignore inference constraints and don't have to special-case
// constraining the normalized-to `term`.
self.filter_specialized_impls(AllowInferenceConstraints::Yes, &mut candidates);
let responses: Vec<_> = candidates.iter().map(|c| c.result).collect();
if let Some(response) = self.try_merge_responses(&responses) {
Ok(response)

View File

@@ -16,6 +16,7 @@ use rustc_type_ir::{
};
use tracing::{instrument, trace};
use super::has_only_region_constraints;
use crate::coherence;
use crate::delegate::SolverDelegate;
use crate::solve::inspect::{self, ProofTreeBuilder};
@@ -476,13 +477,8 @@ where
Ok(response) => response,
};
let has_changed = if !response.value.var_values.is_identity_modulo_regions()
|| !response.value.external_constraints.opaque_types.is_empty()
{
HasChanged::Yes
} else {
HasChanged::No
};
let has_changed =
if !has_only_region_constraints(response) { HasChanged::Yes } else { HasChanged::No };
let (normalization_nested_goals, certainty) =
self.instantiate_and_apply_query_response(goal.param_env, orig_values, response);

View File

@@ -70,6 +70,17 @@ fn has_no_inference_or_external_constraints<I: Interner>(
&& normalization_nested_goals.is_empty()
}
fn has_only_region_constraints<I: Interner>(response: ty::Canonical<I, Response<I>>) -> bool {
let ExternalConstraintsData {
region_constraints: _,
ref opaque_types,
ref normalization_nested_goals,
} = *response.value.external_constraints;
response.value.var_values.is_identity_modulo_regions()
&& opaque_types.is_empty()
&& normalization_nested_goals.is_empty()
}
impl<'a, D, I> EvalCtxt<'a, D>
where
D: SolverDelegate<Interner = I>,

View File

@@ -213,9 +213,6 @@ where
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
};
// In case the associated item is hidden due to specialization, we have to
// return ambiguity this would otherwise be incomplete, resulting in
// unsoundness during coherence (#105782).
let target_item_def_id = match ecx.fetch_eligible_assoc_item(
goal_trait_ref,
goal.predicate.def_id(),
@@ -223,8 +220,28 @@ where
) {
Ok(Some(target_item_def_id)) => target_item_def_id,
Ok(None) => {
return ecx
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
match ecx.typing_mode() {
// In case the associated item is hidden due to specialization, we have to
// return ambiguity this would otherwise be incomplete, resulting in
// unsoundness during coherence (#105782).
ty::TypingMode::Coherence => {
return ecx.evaluate_added_goals_and_make_canonical_response(
Certainty::AMBIGUOUS,
);
}
// Outside of coherence, we treat the associated item as rigid instead.
ty::TypingMode::Analysis { .. }
| ty::TypingMode::Borrowck { .. }
| ty::TypingMode::PostBorrowckAnalysis { .. }
| ty::TypingMode::PostAnalysis => {
ecx.structurally_instantiate_normalizes_to_term(
goal,
goal.predicate.alias,
);
return ecx
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
}
};
}
Err(guar) => return error_response(ecx, guar),
};

View File

@@ -13,7 +13,7 @@ use tracing::{instrument, trace};
use crate::delegate::SolverDelegate;
use crate::solve::assembly::structural_traits::{self, AsyncCallableRelevantTypes};
use crate::solve::assembly::{self, AssembleCandidatesFrom, Candidate};
use crate::solve::assembly::{self, AllowInferenceConstraints, AssembleCandidatesFrom, Candidate};
use crate::solve::inspect::ProbeKind;
use crate::solve::{
BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause,
@@ -1338,6 +1338,8 @@ where
};
}
self.filter_specialized_impls(AllowInferenceConstraints::No, &mut candidates);
// If there are *only* global where bounds, then make sure to return that this
// is still reported as being proven-via the param-env so that rigid projections
// operate correctly. Otherwise, drop all global where-bounds before merging the