Skip to content

Commit 037ac33

Browse files
committed
do not prefer trivially true candidates
1 parent 8d1a764 commit 037ac33

6 files changed

Lines changed: 152 additions & 173 deletions

File tree

‎compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,7 +1335,7 @@ where
13351335
candidates.retain(|c| matches!(c.source, CandidateSource::ParamEnv(_)));
13361336
}
13371337

1338-
if let Some((response, _)) = self.try_merge_candidates(&candidates) {
1338+
if let Some(response) = self.try_merge_candidates(&candidates) {
13391339
Ok(response)
13401340
} else {
13411341
self.flounder(&candidates).map_err(Into::into)
@@ -1357,7 +1357,7 @@ where
13571357
// means we can just ignore inference constraints and don't have to special-case
13581358
// constraining the normalized-to `term`.
13591359
self.filter_specialized_impls(AllowInferenceConstraints::Yes, &mut candidates);
1360-
if let Some((response, _)) = self.try_merge_candidates(&candidates) {
1360+
if let Some(response) = self.try_merge_candidates(&candidates) {
13611361
Ok(response)
13621362
} else {
13631363
self.flounder(&candidates).map_err(Into::into)

‎compiler/rustc_next_trait_solver/src/solve/mod.rs‎

Lines changed: 115 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ mod trait_goals;
2323
use derive_where::derive_where;
2424
use rustc_type_ir::inherent::*;
2525
pub use rustc_type_ir::solve::*;
26-
use rustc_type_ir::{self as ty, Const, Interner, Region, TypeVisitableExt};
26+
use rustc_type_ir::{
27+
self as ty, BoundVarIndexKind, Const, Interner, Region, TypeFlags, TypeSuperVisitable,
28+
TypeVisitable, TypeVisitableExt, TypeVisitor,
29+
};
2730
use tracing::instrument;
2831

2932
pub use self::eval_ctxt::{
@@ -86,25 +89,101 @@ fn equal_response_modulo_region_constraints<I: Interner>(
8689
b: &CanonicalResponse<I>,
8790
) -> bool {
8891
let CanonicalResponse {
89-
max_universe: a_max_universe,
90-
var_kinds: a_var_kinds,
91-
value:
92-
Response {
93-
var_values: a_var_values,
94-
certainty: a_certainty,
95-
external_constraints: a_external_constraints,
96-
},
92+
max_universe: _, // implied by `var_kinds` being equal.
93+
var_kinds,
94+
value: Response { var_values, certainty, external_constraints },
9795
} = a;
9896

9997
let ExternalConstraintsData { region_constraints: _, opaque_types, normalization_nested_goals } =
100-
&**a_external_constraints;
98+
&**external_constraints;
10199

102-
a_max_universe == &b.max_universe
103-
&& a_var_kinds == &b.var_kinds
104-
&& a_var_values == &b.value.var_values
105-
&& a_certainty == &b.value.certainty
100+
let eq_up_to_var_kinds = var_values == &b.value.var_values
101+
&& certainty == &b.value.certainty
106102
&& opaque_types == &b.value.external_constraints.opaque_types
107-
&& normalization_nested_goals == &b.value.external_constraints.normalization_nested_goals
103+
&& normalization_nested_goals == &b.value.external_constraints.normalization_nested_goals;
104+
105+
if !eq_up_to_var_kinds {
106+
return false;
107+
}
108+
109+
if var_kinds == &b.var_kinds {
110+
true
111+
} else {
112+
// If only the `var_kinds` are different, we have to check whether they only
113+
// differ due to region constraints.
114+
//
115+
// FIXE: This is only only sufficient if region constraints are canonicalized
116+
// last, which they are not. This means this check incorrectly returns `false` if
117+
// either the `opaque_types` or `normalization_nested_goals` reference variables
118+
// not mentioned before. The only way this happens is if they mention placeholders
119+
// or higher-ranked existential variables created inside of the query. This feels
120+
// very unlikely and maybe not even worth fixing.
121+
let num_shared: usize =
122+
var_kinds.iter().zip(b.var_kinds.iter()).take_while(|(a, b)| a == b).count();
123+
response_only_references_shared_vars_outside_of_region_constraints(num_shared, a)
124+
&& response_only_references_shared_vars_outside_of_region_constraints(num_shared, b)
125+
}
126+
}
127+
128+
fn response_only_references_shared_vars_outside_of_region_constraints<I: Interner>(
129+
num_shared: usize,
130+
response: &CanonicalResponse<I>,
131+
) -> bool {
132+
struct ReferencesVarKindGtNumShared {
133+
num_shared: usize,
134+
}
135+
impl<I: Interner> TypeVisitor<I> for ReferencesVarKindGtNumShared {
136+
type Result = Result<(), ()>;
137+
138+
fn visit_ty(&mut self, t: I::Ty) -> Result<(), ()> {
139+
if let ty::Bound(BoundVarIndexKind::Canonical, b) = t.kind() {
140+
if b.var.as_usize() >= self.num_shared { Err(()) } else { Ok(()) }
141+
} else if t.has_type_flags(TypeFlags::HAS_CANONICAL_BOUND) {
142+
t.super_visit_with(self)
143+
} else {
144+
Ok(())
145+
}
146+
}
147+
148+
fn visit_region(&mut self, r: Region<I>) -> Result<(), ()> {
149+
if let ty::ReBound(BoundVarIndexKind::Canonical, b) = r.kind() {
150+
if b.var.as_usize() >= self.num_shared { Err(()) } else { Ok(()) }
151+
} else {
152+
Ok(())
153+
}
154+
}
155+
156+
fn visit_const(&mut self, c: Const<I>) -> Result<(), ()> {
157+
if let ty::ConstKind::Bound(BoundVarIndexKind::Canonical, b) = c.kind() {
158+
if b.var.as_usize() >= self.num_shared { Err(()) } else { Ok(()) }
159+
} else if c.has_type_flags(TypeFlags::HAS_CANONICAL_BOUND) {
160+
c.super_visit_with(self)
161+
} else {
162+
Ok(())
163+
}
164+
}
165+
166+
fn visit_predicate<P: rustc_type_ir::PredicateProxy<I>>(&mut self, p: P) -> Result<(), ()> {
167+
if p.has_type_flags(TypeFlags::HAS_CANONICAL_BOUND) {
168+
p.super_visit_with(self)
169+
} else {
170+
Ok(())
171+
}
172+
}
173+
}
174+
175+
let mut visitor = ReferencesVarKindGtNumShared { num_shared };
176+
177+
let CanonicalResponse {
178+
max_universe: _,
179+
var_kinds: _,
180+
value: Response { var_values, certainty: _, external_constraints },
181+
} = response;
182+
let ExternalConstraintsData { region_constraints: _, opaque_types, normalization_nested_goals } =
183+
&**external_constraints;
184+
var_values.visit_with(&mut visitor).is_ok()
185+
&& opaque_types.visit_with(&mut visitor).is_ok()
186+
&& normalization_nested_goals.visit_with(&mut visitor).is_ok()
108187
}
109188

110189
impl<'a, D, I> EvalCtxt<'a, D>
@@ -319,12 +398,6 @@ where
319398
}
320399
}
321400

322-
#[derive(Debug)]
323-
enum MergeCandidateInfo {
324-
AlwaysApplicable(usize),
325-
EqualResponse,
326-
}
327-
328401
impl<D, I> EvalCtxt<'_, D>
329402
where
330403
D: SolverDelegate<Interner = I>,
@@ -337,48 +410,33 @@ where
337410
fn try_merge_candidates(
338411
&mut self,
339412
candidates: &[Candidate<I>],
340-
) -> Option<(CanonicalResponse<I>, MergeCandidateInfo)> {
341-
if candidates.is_empty() {
413+
) -> Option<CanonicalResponse<I>> {
414+
let Some((first, rest)) = candidates.split_first() else {
342415
return None;
343-
}
344-
345-
let always_applicable = candidates.iter().enumerate().find(|(_, candidate)| {
346-
candidate.result.value.certainty == Certainty::Yes
347-
&& has_no_inference_or_external_constraints(candidate.result)
348-
});
416+
};
349417

350-
if let Some((i, candidate)) = always_applicable {
351-
return Some((candidate.result, MergeCandidateInfo::AlwaysApplicable(i)));
418+
if rest.iter().any(|candidate| {
419+
!equal_response_modulo_region_constraints(&first.result, &candidate.result)
420+
}) {
421+
return None;
352422
}
353423

354-
let one: CanonicalResponse<I> = candidates[0].result;
355-
356-
if candidates[1..]
357-
.iter()
358-
.all(|candidate| equal_response_modulo_region_constraints(&one, &candidate.result))
359-
{
360-
let region_constraints = &one.value.external_constraints.region_constraints;
361-
if candidates[1..].iter().all(|candidate| {
362-
&candidate.result.value.external_constraints.region_constraints
363-
== region_constraints
364-
}) {
365-
return Some((one, MergeCandidateInfo::EqualResponse));
366-
}
367-
368-
// If candidates differ only in region constraints, their merged region
369-
// constraints are an `Or` of their respective constraints. If one of them
370-
// has no region constraints, the `Or` constraint evaluates to `true`.
371-
//
372-
// This is a special case of `-Zassumptions-on-binders` and should be
373-
// replaced eventually.
374-
if let Some(candidate) = candidates.iter().find(|candidate| {
375-
candidate.result.value.external_constraints.region_constraints.is_empty()
376-
}) {
377-
return Some((candidate.result, MergeCandidateInfo::EqualResponse));
378-
}
424+
let region_constraints = &first.result.value.external_constraints.region_constraints;
425+
if candidates[1..].iter().all(|candidate| {
426+
&candidate.result.value.external_constraints.region_constraints == region_constraints
427+
}) {
428+
return Some(first.result);
379429
}
380430

381-
None
431+
// If candidates differ only in region constraints, their merged region
432+
// constraints are an OR of their respective constraints. We don't support
433+
// OR constraints yet, so the only way we can merge differing candidates
434+
// if one of them has no region constraints as then the OR constraint
435+
// is trivially true.
436+
candidates
437+
.iter()
438+
.find(|c| c.result.value.external_constraints.region_constraints.is_empty())
439+
.map(|c| c.result)
382440
}
383441

384442
fn bail_with_ambiguity(&mut self, candidates: &[Candidate<I>]) -> CanonicalResponse<I> {

‎compiler/rustc_next_trait_solver/src/solve/trait_goals.rs‎

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ use crate::solve::assembly::{
2424
use crate::solve::inspect::ProbeKind;
2525
use crate::solve::{
2626
BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause,
27-
MergeCandidateInfo, NoSolution, ParamEnvSource, StalledOnCoroutines,
28-
has_only_region_constraints,
27+
NoSolution, ParamEnvSource, StalledOnCoroutines, has_only_region_constraints,
2928
};
3029

3130
impl<D, I> assembly::GoalKind<D> for TraitClause<I>
@@ -1578,7 +1577,7 @@ where
15781577
.all(|candidate| matches!(candidate.source, CandidateSource::BuiltinImpl(_)));
15791578

15801579
if is_marker || all_builtin {
1581-
self.try_merge_candidates(candidates).map(|(response, _)| response)
1580+
self.try_merge_candidates(candidates)
15821581
} else if candidates.len() > 1 {
15831582
None
15841583
} else {
@@ -1591,7 +1590,7 @@ where
15911590
candidates: &[Candidate<I>],
15921591
proven_via: TraitGoalProvenVia,
15931592
) -> (CanonicalResponse<I>, Option<TraitGoalProvenVia>) {
1594-
if let Some((response, _)) = self.try_merge_candidates(candidates) {
1593+
if let Some(response) = self.try_merge_candidates(candidates) {
15951594
(response, Some(proven_via))
15961595
} else {
15971596
(self.bail_with_ambiguity(candidates), None)
@@ -1656,6 +1655,7 @@ where
16561655
let alias_bounds: Vec<_> = candidates
16571656
.extract_if(.., |c| matches!(c.source, CandidateSource::AliasBound(..)))
16581657
.collect();
1658+
candidates.into_iter().for_each(|c| self.ignore_candidate_head_usages(c.head_usages));
16591659
return Ok(self.merge_candidates_or_bail_with_ambiguity(
16601660
&alias_bounds,
16611661
TraitGoalProvenVia::AliasBound,
@@ -1671,38 +1671,10 @@ where
16711671
let where_bounds: Vec<_> = candidates
16721672
.extract_if(.., |c| matches!(c.source, CandidateSource::ParamEnv(_)))
16731673
.collect();
1674-
let Some((response, info)) = self.try_merge_candidates(&where_bounds) else {
1674+
candidates.into_iter().for_each(|c| self.ignore_candidate_head_usages(c.head_usages));
1675+
let Some(response) = self.try_merge_candidates(&where_bounds) else {
16751676
return Ok((self.bail_with_ambiguity(&where_bounds), None));
16761677
};
1677-
match info {
1678-
// If there's an always applicable candidate, the result of all
1679-
// other candidates does not matter. This means we can ignore
1680-
// them when checking whether we've reached a fixpoint.
1681-
//
1682-
// We always prefer the first always applicable candidate, even if a
1683-
// later candidate is also always applicable and would result in fewer
1684-
// reruns. We could slightly improve this by e.g. searching for another
1685-
// always applicable candidate which doesn't depend on any cycle heads.
1686-
//
1687-
// NOTE: This is optimization is observable in case there is an always
1688-
// applicable global candidate and another non-global candidate which only
1689-
// applies because of a provisional result. I can't even think of a test
1690-
// case where this would occur and even then, this would not be unsound.
1691-
// Supporting this makes the code more involved, so I am just going to
1692-
// ignore this for now.
1693-
MergeCandidateInfo::AlwaysApplicable(i) => {
1694-
for (j, c) in where_bounds.into_iter().enumerate() {
1695-
if i != j {
1696-
self.ignore_candidate_head_usages(c.head_usages)
1697-
}
1698-
}
1699-
// If a where-bound does not apply, we don't actually get a
1700-
// candidate for it. We manually track the head usages
1701-
// of all failed `ParamEnv` candidates instead.
1702-
self.ignore_candidate_head_usages(failed_candidate_info.param_env_head_usages);
1703-
}
1704-
MergeCandidateInfo::EqualResponse => {}
1705-
}
17061678
return Ok((response, Some(TraitGoalProvenVia::ParamEnv)));
17071679
}
17081680

@@ -1711,6 +1683,7 @@ where
17111683
let alias_bounds: Vec<_> = candidates
17121684
.extract_if(.., |c| matches!(c.source, CandidateSource::AliasBound(_)))
17131685
.collect();
1686+
candidates.into_iter().for_each(|c| self.ignore_candidate_head_usages(c.head_usages));
17141687
return Ok(self.merge_candidates_or_bail_with_ambiguity(
17151688
&alias_bounds,
17161689
TraitGoalProvenVia::AliasBound,
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//@ revisions: current next
2+
//@ ignore-compare-mode-next-solver (explicit revisions)
3+
//@[next] compile-flags: -Znext-solver
4+
//@ check-pass
5+
6+
// When proving `C::Val<'a>: for<'b> PartialEq<C::Val<'b>>` there are
7+
// two candidates:
8+
// - `C::Val<'a>: PartialEq<C::Val<'a>>`
9+
// - `for<'b> C::Val<'a>: PartialEq<C::Val<'b>>`
10+
//
11+
// The first candidate results in an `'!b == 'a` constraint while the
12+
// second one has no region constraints. The constraint of the first
13+
// candidate means the canonical response has an entry for `'b` in its
14+
// `var_values`. This previously caused us to not prefer the second
15+
// constraint.
16+
//
17+
// Now, actually, the first candidate should just result in a leak check
18+
// failure, but doesn't. That's a separate issue though :>
19+
trait Cursor {
20+
type Val<'a>: PartialEq + for<'b> PartialEq<Self::Val<'b>>;
21+
}
22+
impl<C: Cursor> Cursor for &C {
23+
// for<'b> C::Val<'a>: PartialEq<C::Val<'b>>
24+
// - C::Val<'a>: PartialEq<C::Val<'a>>`
25+
// - for<'b> C::Val<'a>: PartialEq<C::Val<'b>>
26+
type Val<'a> = C::Val<'a>;
27+
}
28+
fn main() {}

‎tests/ui/traits/next-solver/cycles/inductive-cycle-but-ok.rs‎

Lines changed: 0 additions & 44 deletions
This file was deleted.

0 commit comments

Comments
 (0)