Skip to content

Commit c30d7e9

Browse files
authored
Rollup merge of rust-lang#109724 - lcnr:prioritize-env, r=compiler-errors
prioritize param env candidates if they don't guide type inference intended to supersede rust-lang#109579. We disable the prioritization during coherence to maintain completeness. Long term we can hopefully replace this hack with adding OR to external constraints at which point the only relevant part when merging responses is whether they guide type inference in the same way. Reuses `try_merge_responses` for assembly and the cleanest way to impl that was to actually split that so that `try_merge_responses` returns `None` if we fail to merge them and then add `flounder` which is used afterwards which is allowed to lower the certainty of our responses. If, in the future, we add the ability to merge candidates `YES: ?0 = Vec<u32>` and `YES: ?0 = Vec<i32>` to `AMBIG: ?0 = Vec<?1>`, this should happen in `flounder`. r? `@compiler-errors` `@BoxyUwU`
2 parents 749b487 + 3fab7f7 commit c30d7e9

16 files changed

+205
-106
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_middle/src/traits/solve.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,19 @@ pub enum Certainty {
5656
impl Certainty {
5757
pub const AMBIGUOUS: Certainty = Certainty::Maybe(MaybeCause::Ambiguity);
5858

59-
/// When proving multiple goals using **AND**, e.g. nested obligations for an impl,
60-
/// use this function to unify the certainty of these goals
61-
pub fn unify_and(self, other: Certainty) -> Certainty {
59+
/// Use this function to merge the certainty of multiple nested subgoals.
60+
///
61+
/// Given an impl like `impl<T: Foo + Bar> Baz for T {}`, we have 2 nested
62+
/// subgoals whenever we use the impl as a candidate: `T: Foo` and `T: Bar`.
63+
/// If evaluating `T: Foo` results in ambiguity and `T: Bar` results in
64+
/// success, we merge these two responses. This results in ambiguity.
65+
///
66+
/// If we unify ambiguity with overflow, we return overflow. This doesn't matter
67+
/// inside of the solver as we distinguish ambiguity from overflow. It does
68+
/// however matter for diagnostics. If `T: Foo` resulted in overflow and `T: Bar`
69+
/// in ambiguity without changing the inference state, we still want to tell the
70+
/// user that `T: Baz` results in overflow.
71+
pub fn unify_with(self, other: Certainty) -> Certainty {
6272
match (self, other) {
6373
(Certainty::Yes, Certainty::Yes) => Certainty::Yes,
6474
(Certainty::Yes, Certainty::Maybe(_)) => other,

compiler/rustc_trait_selection/src/solve/assembly.rs renamed to compiler/rustc_trait_selection/src/solve/assembly/mod.rs

+28-48
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
//! Code shared by trait and projection goals for candidate assembly.
22
33
use super::search_graph::OverflowHandler;
4-
#[cfg(doc)]
5-
use super::trait_goals::structural_traits::*;
64
use super::{EvalCtxt, SolverMode};
5+
use crate::solve::CanonicalResponseExt;
76
use crate::traits::coherence;
8-
use itertools::Itertools;
97
use rustc_data_structures::fx::FxIndexSet;
108
use rustc_hir::def_id::DefId;
119
use rustc_infer::traits::query::NoSolution;
@@ -16,6 +14,8 @@ use rustc_middle::ty::TypeFoldable;
1614
use rustc_middle::ty::{self, Ty, TyCtxt};
1715
use std::fmt::Debug;
1816

17+
pub(super) mod structural_traits;
18+
1919
/// A candidate is a possible way to prove a goal.
2020
///
2121
/// It consists of both the `source`, which describes how that goal would be proven,
@@ -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;
575583
}
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);
588-
}
589-
}
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,
605584
}
585+
self.flounder(&responses)
606586
}
607587
}

compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs renamed to compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::solve::EvalCtxt;
1111
//
1212
// For types with an "existential" binder, i.e. generator witnesses, we also
1313
// instantiate the binder with placeholders eagerly.
14-
pub(super) fn instantiate_constituent_tys_for_auto_trait<'tcx>(
14+
pub(in crate::solve) fn instantiate_constituent_tys_for_auto_trait<'tcx>(
1515
ecx: &EvalCtxt<'_, 'tcx>,
1616
ty: Ty<'tcx>,
1717
) -> Result<Vec<Ty<'tcx>>, NoSolution> {
@@ -87,7 +87,7 @@ pub(super) fn instantiate_constituent_tys_for_auto_trait<'tcx>(
8787
}
8888
}
8989

90-
fn replace_erased_lifetimes_with_bound_vars<'tcx>(
90+
pub(in crate::solve) fn replace_erased_lifetimes_with_bound_vars<'tcx>(
9191
tcx: TyCtxt<'tcx>,
9292
ty: Ty<'tcx>,
9393
) -> ty::Binder<'tcx, Ty<'tcx>> {
@@ -108,7 +108,7 @@ fn replace_erased_lifetimes_with_bound_vars<'tcx>(
108108
ty::Binder::bind_with_vars(ty, bound_vars)
109109
}
110110

111-
pub(super) fn instantiate_constituent_tys_for_sized_trait<'tcx>(
111+
pub(in crate::solve) fn instantiate_constituent_tys_for_sized_trait<'tcx>(
112112
ecx: &EvalCtxt<'_, 'tcx>,
113113
ty: Ty<'tcx>,
114114
) -> Result<Vec<Ty<'tcx>>, NoSolution> {
@@ -158,7 +158,7 @@ pub(super) fn instantiate_constituent_tys_for_sized_trait<'tcx>(
158158
}
159159
}
160160

161-
pub(super) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>(
161+
pub(in crate::solve) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>(
162162
ecx: &EvalCtxt<'_, 'tcx>,
163163
ty: Ty<'tcx>,
164164
) -> Result<Vec<Ty<'tcx>>, NoSolution> {
@@ -224,7 +224,7 @@ pub(super) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>(
224224
}
225225

226226
// Returns a binder of the tupled inputs types and output type from a builtin callable type.
227-
pub(crate) fn extract_tupled_inputs_and_output_from_callable<'tcx>(
227+
pub(in crate::solve) fn extract_tupled_inputs_and_output_from_callable<'tcx>(
228228
tcx: TyCtxt<'tcx>,
229229
self_ty: Ty<'tcx>,
230230
goal_kind: ty::ClosureKind,
@@ -337,7 +337,13 @@ pub(crate) fn extract_tupled_inputs_and_output_from_callable<'tcx>(
337337
/// additional step of eagerly folding the associated types in the where
338338
/// clauses of the impl. In this example, that means replacing
339339
/// `<Self as Foo>::Bar` with `Ty` in the first impl.
340-
pub(crate) fn predicates_for_object_candidate<'tcx>(
340+
///
341+
// FIXME: This is only necessary as `<Self as Trait>::Assoc: ItemBound`
342+
// bounds in impls are trivially proven using the item bound candidates.
343+
// This is unsound in general and once that is fixed, we don't need to
344+
// normalize eagerly here. See https://github.com/lcnr/solver-woes/issues/9
345+
// for more details.
346+
pub(in crate::solve) fn predicates_for_object_candidate<'tcx>(
341347
ecx: &EvalCtxt<'_, 'tcx>,
342348
param_env: ty::ParamEnv<'tcx>,
343349
trait_ref: ty::TraitRef<'tcx>,

compiler/rustc_trait_selection/src/solve/eval_ctxt.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
357357
// deal with `has_changed` in the next iteration.
358358
new_goals.normalizes_to_hack_goal =
359359
Some(this.resolve_vars_if_possible(goal));
360-
has_changed = has_changed.map_err(|c| c.unify_and(certainty));
360+
has_changed = has_changed.map_err(|c| c.unify_with(certainty));
361361
}
362362
}
363363
}
@@ -378,7 +378,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
378378
Certainty::Yes => {}
379379
Certainty::Maybe(_) => {
380380
new_goals.goals.push(goal);
381-
has_changed = has_changed.map_err(|c| c.unify_and(certainty));
381+
has_changed = has_changed.map_err(|c| c.unify_with(certainty));
382382
}
383383
}
384384
}

compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
5050
certainty: Certainty,
5151
) -> QueryResult<'tcx> {
5252
let goals_certainty = self.try_evaluate_added_goals()?;
53-
let certainty = certainty.unify_and(goals_certainty);
53+
let certainty = certainty.unify_with(goals_certainty);
5454

5555
let external_constraints = self.compute_external_query_constraints()?;
5656

compiler/rustc_trait_selection/src/solve/mod.rs

+58-33
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| {
318-
certainty.unify_and(response.value.certainty)
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| {
344+
certainty.unify_with(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

compiler/rustc_trait_selection/src/solve/project_goals.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use crate::traits::specialization_graph;
22

3-
use super::assembly;
4-
use super::trait_goals::structural_traits;
3+
use super::assembly::{self, structural_traits};
54
use super::EvalCtxt;
65
use rustc_errors::ErrorGuaranteed;
76
use rustc_hir::def::DefKind;

0 commit comments

Comments
 (0)