Skip to content

Commit 2515845

Browse files
authored
Rollup merge of rust-lang#120836 - lcnr:param-env-hide-impl, r=BoxyUwU
hide impls if trait bound is proven from env AVERT YOUR EYES `@compiler-errors` fixes rust-lang/trait-system-refactor-initiative#76 and rust-lang/trait-system-refactor-initiative#12 (comment) this is kinda ugly and I hate it, but I wasn't able to think of a cleaner approach for now. I am also unsure whether we have to refine this filtering later on, so by making the change pretty minimal it should be easier to improve going forward. r? `@BoxyUwU`
2 parents c938624 + 5051637 commit 2515845

18 files changed

+388
-128
lines changed

Diff for: compiler/rustc_trait_selection/src/solve/assembly/mod.rs

+72-46
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_infer::traits::query::NoSolution;
88
use rustc_infer::traits::Reveal;
99
use rustc_middle::traits::solve::inspect::ProbeKind;
1010
use rustc_middle::traits::solve::{
11-
CandidateSource, CanonicalResponse, Certainty, Goal, QueryResult,
11+
CandidateSource, CanonicalResponse, Certainty, Goal, MaybeCause, QueryResult,
1212
};
1313
use rustc_middle::traits::BuiltinImplSource;
1414
use rustc_middle::ty::fast_reject::{SimplifiedType, TreatParams};
@@ -276,25 +276,16 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
276276
&mut self,
277277
goal: Goal<'tcx, G>,
278278
) -> Vec<Candidate<'tcx>> {
279-
let dummy_candidate = |this: &mut EvalCtxt<'_, 'tcx>, certainty| {
280-
let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc);
281-
let result = this.evaluate_added_goals_and_make_canonical_response(certainty).unwrap();
282-
let mut dummy_probe = this.inspect.new_probe();
283-
dummy_probe.probe_kind(ProbeKind::TraitCandidate { source, result: Ok(result) });
284-
this.inspect.finish_probe(dummy_probe);
285-
vec![Candidate { source, result }]
286-
};
287-
288279
let Some(normalized_self_ty) =
289280
self.try_normalize_ty(goal.param_env, goal.predicate.self_ty())
290281
else {
291282
debug!("overflow while evaluating self type");
292-
return dummy_candidate(self, Certainty::OVERFLOW);
283+
return self.forced_ambiguity(MaybeCause::Overflow);
293284
};
294285

295286
if normalized_self_ty.is_ty_var() {
296287
debug!("self type has been normalized to infer");
297-
return dummy_candidate(self, Certainty::AMBIGUOUS);
288+
return self.forced_ambiguity(MaybeCause::Ambiguity);
298289
}
299290

300291
let goal =
@@ -315,11 +306,26 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
315306

316307
self.assemble_param_env_candidates(goal, &mut candidates);
317308

318-
self.assemble_coherence_unknowable_candidates(goal, &mut candidates);
309+
match self.solver_mode() {
310+
SolverMode::Normal => self.discard_impls_shadowed_by_env(goal, &mut candidates),
311+
SolverMode::Coherence => {
312+
self.assemble_coherence_unknowable_candidates(goal, &mut candidates)
313+
}
314+
}
319315

320316
candidates
321317
}
322318

319+
fn forced_ambiguity(&mut self, cause: MaybeCause) -> Vec<Candidate<'tcx>> {
320+
let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc);
321+
let certainty = Certainty::Maybe(cause);
322+
let result = self.evaluate_added_goals_and_make_canonical_response(certainty).unwrap();
323+
let mut dummy_probe = self.inspect.new_probe();
324+
dummy_probe.probe_kind(ProbeKind::TraitCandidate { source, result: Ok(result) });
325+
self.inspect.finish_probe(dummy_probe);
326+
vec![Candidate { source, result }]
327+
}
328+
323329
#[instrument(level = "debug", skip_all)]
324330
fn assemble_non_blanket_impl_candidates<G: GoalKind<'tcx>>(
325331
&mut self,
@@ -779,18 +785,19 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
779785
}
780786
}
781787

788+
/// In coherence we have to not only care about all impls we know about, but
789+
/// also consider impls which may get added in a downstream or sibling crate
790+
/// or which an upstream impl may add in a minor release.
791+
///
792+
/// To do so we add an ambiguous candidate in case such an unknown impl could
793+
/// apply to the current goal.
782794
#[instrument(level = "debug", skip_all)]
783795
fn assemble_coherence_unknowable_candidates<G: GoalKind<'tcx>>(
784796
&mut self,
785797
goal: Goal<'tcx, G>,
786798
candidates: &mut Vec<Candidate<'tcx>>,
787799
) {
788800
let tcx = self.tcx();
789-
match self.solver_mode() {
790-
SolverMode::Normal => return,
791-
SolverMode::Coherence => {}
792-
};
793-
794801
let result = self.probe_misc_candidate("coherence unknowable").enter(|ecx| {
795802
let trait_ref = goal.predicate.trait_ref(tcx);
796803
#[derive(Debug)]
@@ -820,6 +827,51 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
820827
}
821828
}
822829

830+
/// If there's a where-bound for the current goal, do not use any impl candidates
831+
/// to prove the current goal. Most importantly, if there is a where-bound which does
832+
/// not specify any associated types, we do not allow normalizing the associated type
833+
/// by using an impl, even if it would apply.
834+
///
835+
/// <https://github.com/rust-lang/trait-system-refactor-initiative/issues/76>
836+
// FIXME(@lcnr): The current structure here makes me unhappy and feels ugly. idk how
837+
// to improve this however. However, this should make it fairly straightforward to refine
838+
// the filtering going forward, so it seems alright-ish for now.
839+
fn discard_impls_shadowed_by_env<G: GoalKind<'tcx>>(
840+
&mut self,
841+
goal: Goal<'tcx, G>,
842+
candidates: &mut Vec<Candidate<'tcx>>,
843+
) {
844+
let tcx = self.tcx();
845+
let trait_goal: Goal<'tcx, ty::TraitPredicate<'tcx>> =
846+
goal.with(tcx, goal.predicate.trait_ref(tcx));
847+
let mut trait_candidates_from_env = Vec::new();
848+
self.assemble_param_env_candidates(trait_goal, &mut trait_candidates_from_env);
849+
self.assemble_alias_bound_candidates(trait_goal, &mut trait_candidates_from_env);
850+
if !trait_candidates_from_env.is_empty() {
851+
let trait_env_result = self.merge_candidates(trait_candidates_from_env);
852+
match trait_env_result.unwrap().value.certainty {
853+
// If proving the trait goal succeeds by using the env,
854+
// we freely drop all impl candidates.
855+
//
856+
// FIXME(@lcnr): It feels like this could easily hide
857+
// a forced ambiguity candidate added earlier.
858+
// This feels dangerous.
859+
Certainty::Yes => {
860+
candidates.retain(|c| match c.source {
861+
CandidateSource::Impl(_) | CandidateSource::BuiltinImpl(_) => false,
862+
CandidateSource::ParamEnv(_) | CandidateSource::AliasBound => true,
863+
});
864+
}
865+
// If it is still ambiguous we instead just force the whole goal
866+
// to be ambig and wait for inference constraints. See
867+
// tests/ui/traits/next-solver/env-shadows-impls/ambig-env-no-shadow.rs
868+
Certainty::Maybe(cause) => {
869+
*candidates = self.forced_ambiguity(cause);
870+
}
871+
}
872+
}
873+
}
874+
823875
/// If there are multiple ways to prove a trait or projection goal, we have
824876
/// to somehow try to merge the candidates into one. If that fails, we return
825877
/// ambiguity.
@@ -832,34 +884,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
832884
let responses = candidates.iter().map(|c| c.result).collect::<Vec<_>>();
833885
if let Some(result) = self.try_merge_responses(&responses) {
834886
return Ok(result);
887+
} else {
888+
self.flounder(&responses)
835889
}
836-
837-
// We then check whether we should prioritize `ParamEnv` candidates.
838-
//
839-
// Doing so is incomplete and would therefore be unsound during coherence.
840-
match self.solver_mode() {
841-
SolverMode::Coherence => (),
842-
// Prioritize `ParamEnv` candidates only if they do not guide inference.
843-
//
844-
// This is still incomplete as we may add incorrect region bounds.
845-
SolverMode::Normal => {
846-
let param_env_responses = candidates
847-
.iter()
848-
.filter(|c| {
849-
matches!(
850-
c.source,
851-
CandidateSource::ParamEnv(_) | CandidateSource::AliasBound
852-
)
853-
})
854-
.map(|c| c.result)
855-
.collect::<Vec<_>>();
856-
if let Some(result) = self.try_merge_responses(&param_env_responses) {
857-
// We strongly prefer alias and param-env bounds here, even if they affect inference.
858-
// See https://github.com/rust-lang/trait-system-refactor-initiative/issues/11.
859-
return Ok(result);
860-
}
861-
}
862-
}
863-
self.flounder(&responses)
864890
}
865891
}

Diff for: tests/ui/traits/next-solver/cycles/fixpoint-rerun-all-cycle-heads.rs

+12
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ where
2424
{
2525
}
2626

27+
// HACK: This impls is necessary so that the impl above is well-formed.
28+
//
29+
// When checking that the impl above is well-formed we check `B<T>: Trait<'a, 'b>`
30+
// with the where clauses `A<T>: Trait<'a, 'b>` and `A<T> NotImplemented`. Trying to
31+
// use the impl itself to prove that adds region constraints as we uniquified the
32+
// regions in the `A<T>: Trait<'a, 'b>` where-bound. As both the impl above
33+
// and the impl below now apply with some constraints, we failed with ambiguity.
34+
impl<'a, 'b, T: ?Sized> Trait<'a, 'b> for B<T>
35+
where
36+
A<T>: NotImplemented,
37+
{}
38+
2739
// This impl directly requires 'b to be equal to 'static.
2840
//
2941
// Because of the coinductive cycle through `C<T>` it also requires

Diff for: tests/ui/traits/next-solver/cycles/fixpoint-rerun-all-cycle-heads.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: lifetime may not live long enough
2-
--> $DIR/fixpoint-rerun-all-cycle-heads.rs:47:5
2+
--> $DIR/fixpoint-rerun-all-cycle-heads.rs:59:5
33
|
44
LL | fn check<'a, T: ?Sized>() {
55
| -- lifetime `'a` defined here
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// compile-flags: -Znext-solver
2+
// check-pass
3+
4+
// If a trait goal is proven using the environment, we discard
5+
// impl candidates when normalizing. However, in this example
6+
// the env candidates start as ambiguous and end up not applying,
7+
// so normalization should succeed later on.
8+
9+
trait Trait<T>: Sized {
10+
type Assoc: From<Self>;
11+
}
12+
13+
impl<T, U> Trait<U> for T {
14+
type Assoc = T;
15+
}
16+
17+
fn mk_assoc<T: Trait<U>, U>(t: T, _: U) -> <T as Trait<U>>::Assoc {
18+
t.into()
19+
}
20+
21+
fn generic<T>(t: T) -> T
22+
where
23+
T: Trait<u32>,
24+
T: Trait<i16>,
25+
{
26+
let u = Default::default();
27+
28+
// at this point we have 2 ambig env candidates
29+
let ret: T = mk_assoc(t, u);
30+
31+
// now both env candidates don't apply, so we're now able to
32+
// normalize using this impl candidates. For this to work
33+
// the normalizes-to must have remained ambiguous above.
34+
let _: u8 = u;
35+
ret
36+
}
37+
38+
fn main() {
39+
assert_eq!(generic(1), 1);
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// compile-flags: -Znext-solver
2+
// check-pass
3+
4+
// Normalizing `<T as Trait>::TraitAssoc` in the elaborated environment
5+
// `[T: Trait, T: Super, <T as Super>::SuperAssoc = <T as Trait>::TraitAssoc]`
6+
// has a single impl candidate, which uses the environment to
7+
// normalize `<T as Trait>::TraitAssoc` to itself. We avoid this overflow
8+
// by discarding impl candidates the trait bound is proven by a where-clause.
9+
10+
// https://github.com/rust-lang/trait-system-refactor-initiative/issues/76
11+
trait Super {
12+
type SuperAssoc;
13+
}
14+
15+
trait Trait: Super<SuperAssoc = Self::TraitAssoc> {
16+
type TraitAssoc;
17+
}
18+
19+
impl<T, U> Trait for T
20+
where
21+
T: Super<SuperAssoc = U>,
22+
{
23+
type TraitAssoc = U;
24+
}
25+
26+
fn overflow<T: Trait>() {
27+
let x: <T as Trait>::TraitAssoc;
28+
}
29+
30+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// revisions: next current
2+
//[next] compile-flags: -Znext-solver
3+
// check-pass
4+
5+
#![allow(warnings)]
6+
trait Trait<U> {
7+
type Assoc;
8+
}
9+
10+
impl<T> Trait<u64> for T {
11+
type Assoc = T;
12+
}
13+
14+
fn lazy_init<T: Trait<U>, U>() -> (T, <T as Trait<U>>::Assoc) {
15+
todo!()
16+
}
17+
18+
fn foo<T: Trait<u32, Assoc = T>>(x: T) {
19+
// When considering impl candidates to be equally valid as env candidates
20+
// this ends up being ambiguous as `U` can be both `u32´ and `u64` here.
21+
//
22+
// This is acceptable breakage but we should still note that it's
23+
// theoretically breaking.
24+
let (delayed, mut proj) = lazy_init::<_, _>();
25+
proj = x;
26+
let _: T = delayed;
27+
}
28+
29+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// compile-flags: -Znext-solver
2+
// check-pass
3+
4+
// If we normalize using the impl here the constraints from normalization and
5+
// trait goals can differ. This is especially bad if normalization results
6+
// in stronger constraints.
7+
trait Trait<'a> {
8+
type Assoc;
9+
}
10+
11+
impl<T> Trait<'static> for T {
12+
type Assoc = ();
13+
}
14+
15+
// normalizing requires `'a == 'static`, the trait bound does not.
16+
fn foo<'a, T: Trait<'a>>(_: T::Assoc) {}
17+
18+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// compile-flags: -Znext-solver
2+
3+
// Checks whether the new solver is smart enough to infer `?0 = U` when solving:
4+
// `normalizes-to(<Vec<?0> as Trait>::Assoc, u8)`
5+
// with `normalizes-to(<Vec<U> as Trait>::Assoc, u8)` in the paramenv even when
6+
// there is a separate `Vec<T>: Trait` bound in the paramenv.
7+
//
8+
// We currently intentionally do not guide inference this way.
9+
10+
trait Trait {
11+
type Assoc;
12+
}
13+
14+
fn foo<T: Trait<Assoc = u8>>(x: T) {}
15+
16+
fn unconstrained<T>() -> Vec<T> {
17+
todo!()
18+
}
19+
20+
fn bar<T, U>()
21+
where
22+
Vec<T>: Trait,
23+
Vec<U>: Trait<Assoc = u8>,
24+
{
25+
foo(unconstrained())
26+
//~^ ERROR type annotations needed
27+
}
28+
29+
fn main() {}

Diff for: tests/ui/traits/next-solver/normalizes_to_ignores_unnormalizable_candidate.self_infer.stderr renamed to tests/ui/traits/next-solver/env-shadows-impls/normalizes_to_ignores_unnormalizable_candidate.stderr

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
error[E0283]: type annotations needed
2-
--> $DIR/normalizes_to_ignores_unnormalizable_candidate.rs:36:5
2+
--> $DIR/normalizes_to_ignores_unnormalizable_candidate.rs:25:5
33
|
44
LL | foo(unconstrained())
55
| ^^^ --------------- type must be known at this point
66
| |
77
| cannot infer type of the type parameter `T` declared on the function `foo`
88
|
9-
= note: cannot satisfy `_: Trait`
9+
= note: cannot satisfy `Vec<_>: Trait`
1010
note: required by a bound in `foo`
11-
--> $DIR/normalizes_to_ignores_unnormalizable_candidate.rs:19:11
11+
--> $DIR/normalizes_to_ignores_unnormalizable_candidate.rs:14:11
1212
|
1313
LL | fn foo<T: Trait<Assoc = u8>>(x: T) {}
1414
| ^^^^^^^^^^^^^^^^^ required by this bound in `foo`
1515
help: consider specifying the generic argument
1616
|
17-
LL | foo::<T>(unconstrained())
18-
| +++++
17+
LL | foo::<Vec<T>>(unconstrained())
18+
| ++++++++++
1919

2020
error: aborting due to 1 previous error
2121

0 commit comments

Comments
 (0)