Skip to content

Commit 2b0f572

Browse files
committed
prioritize param-env candidates
1 parent 749b487 commit 2b0f572

File tree

8 files changed

+148
-85
lines changed

8 files changed

+148
-85
lines changed

compiler/rustc_middle/src/infer/canonical.rs

+12
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,18 @@ impl CanonicalVarValues<'_> {
8080
}
8181
})
8282
}
83+
84+
pub fn is_identity_modulo_regions(&self) -> bool {
85+
self.var_values.iter().enumerate().all(|(bv, arg)| match arg.unpack() {
86+
ty::GenericArgKind::Lifetime(_) => true,
87+
ty::GenericArgKind::Type(ty) => {
88+
matches!(*ty.kind(), ty::Bound(ty::INNERMOST, bt) if bt.var.as_usize() == bv)
89+
}
90+
ty::GenericArgKind::Const(ct) => {
91+
matches!(ct.kind(), ty::ConstKind::Bound(ty::INNERMOST, bc) if bc.as_usize() == bv)
92+
}
93+
})
94+
}
8395
}
8496

8597
/// When we canonicalize a value to form a query, we wind up replacing

compiler/rustc_trait_selection/src/solve/assembly.rs

+26-46
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use super::search_graph::OverflowHandler;
44
#[cfg(doc)]
55
use super::trait_goals::structural_traits::*;
66
use super::{EvalCtxt, SolverMode};
7+
use crate::solve::CanonicalResponseExt;
78
use crate::traits::coherence;
8-
use itertools::Itertools;
99
use rustc_data_structures::fx::FxIndexSet;
1010
use rustc_hir::def_id::DefId;
1111
use rustc_infer::traits::query::NoSolution;
@@ -547,61 +547,41 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
547547
}
548548
}
549549

550+
/// If there are multiple ways to prove a trait or projection goal, we have
551+
/// to somehow try to merge the candidates into one. If that fails, we return
552+
/// ambiguity.
550553
#[instrument(level = "debug", skip(self), ret)]
551554
pub(super) fn merge_candidates(
552555
&mut self,
553556
mut candidates: Vec<Candidate<'tcx>>,
554557
) -> QueryResult<'tcx> {
555-
match candidates.len() {
556-
0 => return Err(NoSolution),
557-
1 => return Ok(candidates.pop().unwrap().result),
558-
_ => {}
558+
// First try merging all candidates. This is complete and fully sound.
559+
let responses = candidates.iter().map(|c| c.result).collect::<Vec<_>>();
560+
if let Some(result) = self.try_merge_responses(&responses) {
561+
return Ok(result);
559562
}
560563

561-
if candidates.len() > 1 {
562-
let mut i = 0;
563-
'outer: while i < candidates.len() {
564-
for j in (0..candidates.len()).filter(|&j| i != j) {
565-
if self.candidate_should_be_dropped_in_favor_of(&candidates[i], &candidates[j])
566-
{
567-
debug!(candidate = ?candidates[i], "Dropping candidate #{}/{}", i, candidates.len());
568-
candidates.swap_remove(i);
569-
continue 'outer;
564+
// We then check whether we should prioritize `ParamEnv` candidates.
565+
//
566+
// Doing so is incomplete and would therefore be unsound during coherence.
567+
match self.solver_mode() {
568+
SolverMode::Coherence => (),
569+
// Prioritize `ParamEnv` candidates only if they do not guide inference.
570+
//
571+
// This is still incomplete as we may add incorrect region bounds.
572+
SolverMode::Normal => {
573+
let param_env_responses = candidates
574+
.iter()
575+
.filter(|c| matches!(c.source, CandidateSource::ParamEnv(_)))
576+
.map(|c| c.result)
577+
.collect::<Vec<_>>();
578+
if let Some(result) = self.try_merge_responses(&param_env_responses) {
579+
if result.has_only_region_constraints() {
580+
return Ok(result);
570581
}
571582
}
572-
573-
debug!(candidate = ?candidates[i], "Retaining candidate #{}/{}", i, candidates.len());
574-
i += 1;
575-
}
576-
577-
// If there are *STILL* multiple candidates that have *different* response
578-
// results, give up and report ambiguity.
579-
if candidates.len() > 1 && !candidates.iter().map(|cand| cand.result).all_equal() {
580-
let certainty = if candidates.iter().all(|x| {
581-
matches!(x.result.value.certainty, Certainty::Maybe(MaybeCause::Overflow))
582-
}) {
583-
Certainty::Maybe(MaybeCause::Overflow)
584-
} else {
585-
Certainty::AMBIGUOUS
586-
};
587-
return self.evaluate_added_goals_and_make_canonical_response(certainty);
588583
}
589584
}
590-
591-
Ok(candidates.pop().unwrap().result)
592-
}
593-
594-
fn candidate_should_be_dropped_in_favor_of(
595-
&self,
596-
candidate: &Candidate<'tcx>,
597-
other: &Candidate<'tcx>,
598-
) -> bool {
599-
// FIXME: implement this
600-
match (candidate.source, other.source) {
601-
(CandidateSource::Impl(_), _)
602-
| (CandidateSource::ParamEnv(_), _)
603-
| (CandidateSource::AliasBound, _)
604-
| (CandidateSource::BuiltinImpl, _) => false,
605-
}
585+
self.flounder(&responses)
606586
}
607587
}

compiler/rustc_trait_selection/src/solve/mod.rs

+57-32
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ enum SolverMode {
4646

4747
trait CanonicalResponseExt {
4848
fn has_no_inference_or_external_constraints(&self) -> bool;
49+
50+
fn has_only_region_constraints(&self) -> bool;
4951
}
5052

5153
impl<'tcx> CanonicalResponseExt for Canonical<'tcx, Response<'tcx>> {
@@ -54,6 +56,11 @@ impl<'tcx> CanonicalResponseExt for Canonical<'tcx, Response<'tcx>> {
5456
&& self.value.var_values.is_identity()
5557
&& self.value.external_constraints.opaque_types.is_empty()
5658
}
59+
60+
fn has_only_region_constraints(&self) -> bool {
61+
self.value.var_values.is_identity_modulo_regions()
62+
&& self.value.external_constraints.opaque_types.is_empty()
63+
}
5764
}
5865

5966
impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
@@ -221,12 +228,17 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
221228
(Some(alias_lhs), Some(alias_rhs)) => {
222229
debug!("both sides are aliases");
223230

224-
let candidates = vec![
225-
// LHS normalizes-to RHS
226-
evaluate_normalizes_to(self, alias_lhs, rhs, direction, Invert::No),
227-
// RHS normalizes-to RHS
228-
evaluate_normalizes_to(self, alias_rhs, lhs, direction, Invert::Yes),
229-
// Relate via substs
231+
let mut candidates = Vec::new();
232+
// LHS normalizes-to RHS
233+
candidates.extend(
234+
evaluate_normalizes_to(self, alias_lhs, rhs, direction, Invert::No).ok(),
235+
);
236+
// RHS normalizes-to RHS
237+
candidates.extend(
238+
evaluate_normalizes_to(self, alias_rhs, lhs, direction, Invert::Yes).ok(),
239+
);
240+
// Relate via substs
241+
candidates.extend(
230242
self.probe(|ecx| {
231243
let span = tracing::span!(
232244
tracing::Level::DEBUG,
@@ -247,11 +259,16 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
247259
}
248260

249261
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
250-
}),
251-
];
262+
})
263+
.ok(),
264+
);
252265
debug!(?candidates);
253266

254-
self.try_merge_responses(candidates.into_iter())
267+
if let Some(merged) = self.try_merge_responses(&candidates) {
268+
Ok(merged)
269+
} else {
270+
self.flounder(&candidates)
271+
}
255272
}
256273
}
257274
}
@@ -289,43 +306,51 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
289306
debug!("added_goals={:?}", &self.nested_goals.goals[current_len..]);
290307
}
291308

292-
#[instrument(level = "debug", skip(self, responses))]
309+
/// Try to merge multiple possible ways to prove a goal, if that is not possible returns `None`.
310+
///
311+
/// In this case we tend to flounder and return ambiguity by calling `[EvalCtxt::flounder]`.
312+
#[instrument(level = "debug", skip(self), ret)]
293313
fn try_merge_responses(
294314
&mut self,
295-
responses: impl Iterator<Item = QueryResult<'tcx>>,
296-
) -> QueryResult<'tcx> {
297-
let candidates = responses.into_iter().flatten().collect::<Box<[_]>>();
298-
299-
if candidates.is_empty() {
300-
return Err(NoSolution);
315+
responses: &[CanonicalResponse<'tcx>],
316+
) -> Option<CanonicalResponse<'tcx>> {
317+
if responses.is_empty() {
318+
return None;
301319
}
302320

303321
// FIXME(-Ztrait-solver=next): We should instead try to find a `Certainty::Yes` response with
304322
// a subset of the constraints that all the other responses have.
305-
let one = candidates[0];
306-
if candidates[1..].iter().all(|resp| resp == &one) {
307-
return Ok(one);
323+
let one = responses[0];
324+
if responses[1..].iter().all(|&resp| resp == one) {
325+
return Some(one);
308326
}
309327

310-
if let Some(response) = candidates.iter().find(|response| {
311-
response.value.certainty == Certainty::Yes
312-
&& response.has_no_inference_or_external_constraints()
313-
}) {
314-
return Ok(*response);
315-
}
328+
responses
329+
.iter()
330+
.find(|response| {
331+
response.value.certainty == Certainty::Yes
332+
&& response.has_no_inference_or_external_constraints()
333+
})
334+
.copied()
335+
}
316336

317-
let certainty = candidates.iter().fold(Certainty::AMBIGUOUS, |certainty, response| {
337+
/// If we fail to merge responses we flounder and return overflow or ambiguity.
338+
#[instrument(level = "debug", skip(self), ret)]
339+
fn flounder(&mut self, responses: &[CanonicalResponse<'tcx>]) -> QueryResult<'tcx> {
340+
if responses.is_empty() {
341+
return Err(NoSolution);
342+
}
343+
let certainty = responses.iter().fold(Certainty::AMBIGUOUS, |certainty, response| {
318344
certainty.unify_and(response.value.certainty)
319345
});
320-
// FIXME(-Ztrait-solver=next): We should take the intersection of the constraints on all the
321-
// responses and use that for the constraints of this ambiguous response.
322-
debug!(">1 response, bailing with {certainty:?}");
346+
323347
let response = self.evaluate_added_goals_and_make_canonical_response(certainty);
324-
if let Ok(response) = &response {
348+
if let Ok(response) = response {
325349
assert!(response.has_no_inference_or_external_constraints());
350+
Ok(response)
351+
} else {
352+
bug!("failed to make floundered response: {responses:?}");
326353
}
327-
328-
response
329354
}
330355
}
331356

tests/ui/traits/new-solver/alias_eq_dont_use_normalizes_to_if_substs_eq.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// compile-flags: -Ztrait-solver=next
22

33
// check that when computing `alias-eq(<() as Foo<u16, T>>::Assoc, <() as Foo<?0, T>>::Assoc)`
4-
// we do not infer `?0 = u8` via the `for<STOP> (): Foo<u8, STOP>` impl or `?0 = u16` by
4+
// we do not infer `?0 = u8` via the `for<STOP> (): Foo<u8, STOP>` impl or `?0 = u16` by
55
// relating substs as either could be a valid solution.
66

77
trait Foo<T, STOP> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// compile-flags: -Ztrait-solver=next
2+
// check-pass
3+
4+
trait Foo {}
5+
6+
impl<T> Foo for T {}
7+
8+
trait Bar {}
9+
10+
struct Wrapper<'a, T>(&'a T);
11+
12+
impl<'a, T> Bar for Wrapper<'a, T> where &'a T: Foo {}
13+
// We need to satisfy `&'a T: Foo` when checking that this impl is WF
14+
// that can either be satisfied via the param-env, or via an impl.
15+
//
16+
// When satisfied via the param-env, since each lifetime is canonicalized
17+
// separately, we end up getting extra region constraints.
18+
//
19+
// However, when satisfied via the impl, there are no region constraints,
20+
// and we can short-circuit a response with no external constraints.
21+
22+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// compile-flags: -Ztrait-solver=next
2+
// check-pass
3+
4+
trait Foo<'a> {}
5+
trait Bar<'a> {}
6+
7+
impl<'a, T: Bar<'a>> Foo<'a> for T {}
8+
impl<T> Bar<'static> for T {}
9+
10+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
error[E0282]: type annotations needed
1+
error[E0283]: type annotations needed: cannot satisfy `<T as Foo1>::Assoc1: Bar`
22
--> $DIR/recursive-self-normalization-2.rs:15:5
33
|
44
LL | needs_bar::<T::Assoc1>();
5-
| ^^^^^^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `S` declared on the function `needs_bar`
5+
| ^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: cannot satisfy `<T as Foo1>::Assoc1: Bar`
8+
note: required by a bound in `needs_bar`
9+
--> $DIR/recursive-self-normalization-2.rs:12:17
10+
|
11+
LL | fn needs_bar<S: Bar>() {}
12+
| ^^^ required by this bound in `needs_bar`
613

714
error: aborting due to previous error
815

9-
For more information about this error, try `rustc --explain E0282`.
16+
For more information about this error, try `rustc --explain E0283`.
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
error[E0282]: type annotations needed
1+
error[E0283]: type annotations needed: cannot satisfy `<T as Foo>::Assoc: Bar`
22
--> $DIR/recursive-self-normalization.rs:11:5
33
|
44
LL | needs_bar::<T::Assoc>();
5-
| ^^^^^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `S` declared on the function `needs_bar`
5+
| ^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: cannot satisfy `<T as Foo>::Assoc: Bar`
8+
note: required by a bound in `needs_bar`
9+
--> $DIR/recursive-self-normalization.rs:8:17
10+
|
11+
LL | fn needs_bar<S: Bar>() {}
12+
| ^^^ required by this bound in `needs_bar`
613

714
error: aborting due to previous error
815

9-
For more information about this error, try `rustc --explain E0282`.
16+
For more information about this error, try `rustc --explain E0283`.

0 commit comments

Comments
 (0)