Skip to content

Commit cbe429c

Browse files
authored
Rollup merge of #112076 - compiler-errors:bidirectional-alias-eq, r=lcnr
Fall back to bidirectional normalizes-to if no subst-relate candidate in alias-relate goal Sometimes we get into the case where the choice of normalizes-to branch in alias-relate are both valid, but we cannot make a choice of which one to take because they are different -- either returning equivalent but permuted region constraints, or equivalent opaque type definitions but differing modulo normalization. In this case, we can make progress by considering a fourth candidate where we compute both normalizes-to branches together and canonicalize that as a response. This is essentially the AND intersection of both normalizes-to branches. In an ideal world, we'd be returning something more like the OR intersection of both branches, but we have no way of representing that either for regions (maybe eventually) or opaques (don't see that happening ever). This is incomplete, so like the subst-relate fallback it's only considered outside of coherence. But it doesn't seem like a dramatic strengthening of inference or anything, and is useful for helping opaque type inference succeed when the hidden type is a projection. ## Example Consider the goal - `AliasRelate(Tait, <[i32; 32] as IntoIterator>::IntoIter)`. We have three ways of currently solving this goal: 1. SubstRelate - fails because we can't directly equate the substs of different alias kinds. 2. NormalizesToRhs - `Tait normalizes-to <[i32; 32] as IntoIterator>::IntoIter` * Ends up infering opaque definition - `Tait := <[i32; 32] as IntoIterator>::IntoIter` 3. NormalizesToLhs - `<[i32; 32] as IntoIterator>::IntoIter normalizes-to Tait` * Find impl candidate, substitute the associated type - `std::array::IntoIter<i32, 32>` * Equate `std::array::IntoIter<i32, 32>` and `Tait` * Ends up infering opaque definition - `Tait := std::array::IntoIter<i32, 32>` The problem here is that 2 and 3 are essentially both valid, since we have aliases that normalize on both sides, but due to lazy norm, they end up inferring different opaque type definitions that are only equal *after* normalizing them further. --- r? `@lcnr`
2 parents e94bda3 + 3ea7c51 commit cbe429c

File tree

5 files changed

+269
-136
lines changed

5 files changed

+269
-136
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
use super::{EvalCtxt, SolverMode};
2+
use rustc_infer::traits::query::NoSolution;
3+
use rustc_middle::traits::solve::{Certainty, Goal, QueryResult};
4+
use rustc_middle::ty;
5+
6+
/// We may need to invert the alias relation direction if dealing an alias on the RHS.
7+
#[derive(Debug)]
8+
enum Invert {
9+
No,
10+
Yes,
11+
}
12+
13+
impl<'tcx> EvalCtxt<'_, 'tcx> {
14+
#[instrument(level = "debug", skip(self), ret)]
15+
pub(super) fn compute_alias_relate_goal(
16+
&mut self,
17+
goal: Goal<'tcx, (ty::Term<'tcx>, ty::Term<'tcx>, ty::AliasRelationDirection)>,
18+
) -> QueryResult<'tcx> {
19+
let tcx = self.tcx();
20+
let Goal { param_env, predicate: (lhs, rhs, direction) } = goal;
21+
if lhs.is_infer() || rhs.is_infer() {
22+
bug!(
23+
"`AliasRelate` goal with an infer var on lhs or rhs which should have been instantiated"
24+
);
25+
}
26+
27+
match (lhs.to_alias_ty(tcx), rhs.to_alias_ty(tcx)) {
28+
(None, None) => bug!("`AliasRelate` goal without an alias on either lhs or rhs"),
29+
30+
// RHS is not a projection, only way this is true is if LHS normalizes-to RHS
31+
(Some(alias_lhs), None) => self.assemble_normalizes_to_candidate(
32+
param_env,
33+
alias_lhs,
34+
rhs,
35+
direction,
36+
Invert::No,
37+
),
38+
39+
// LHS is not a projection, only way this is true is if RHS normalizes-to LHS
40+
(None, Some(alias_rhs)) => self.assemble_normalizes_to_candidate(
41+
param_env,
42+
alias_rhs,
43+
lhs,
44+
direction,
45+
Invert::Yes,
46+
),
47+
48+
(Some(alias_lhs), Some(alias_rhs)) => {
49+
debug!("both sides are aliases");
50+
51+
let mut candidates = Vec::new();
52+
// LHS normalizes-to RHS
53+
candidates.extend(self.assemble_normalizes_to_candidate(
54+
param_env,
55+
alias_lhs,
56+
rhs,
57+
direction,
58+
Invert::No,
59+
));
60+
// RHS normalizes-to RHS
61+
candidates.extend(self.assemble_normalizes_to_candidate(
62+
param_env,
63+
alias_rhs,
64+
lhs,
65+
direction,
66+
Invert::Yes,
67+
));
68+
// Relate via substs
69+
let subst_relate_response = self
70+
.assemble_subst_relate_candidate(param_env, alias_lhs, alias_rhs, direction);
71+
candidates.extend(subst_relate_response);
72+
debug!(?candidates);
73+
74+
if let Some(merged) = self.try_merge_responses(&candidates) {
75+
Ok(merged)
76+
} else {
77+
// When relating two aliases and we have ambiguity, we prefer
78+
// relating the generic arguments of the aliases over normalizing
79+
// them. This is necessary for inference during typeck.
80+
//
81+
// As this is incomplete, we must not do so during coherence.
82+
match self.solver_mode() {
83+
SolverMode::Normal => {
84+
if let Ok(subst_relate_response) = subst_relate_response {
85+
Ok(subst_relate_response)
86+
} else if let Ok(bidirectional_normalizes_to_response) = self
87+
.assemble_bidirectional_normalizes_to_candidate(
88+
param_env, lhs, rhs, direction,
89+
)
90+
{
91+
Ok(bidirectional_normalizes_to_response)
92+
} else {
93+
self.flounder(&candidates)
94+
}
95+
}
96+
SolverMode::Coherence => self.flounder(&candidates),
97+
}
98+
}
99+
}
100+
}
101+
}
102+
103+
#[instrument(level = "debug", skip(self), ret)]
104+
fn assemble_normalizes_to_candidate(
105+
&mut self,
106+
param_env: ty::ParamEnv<'tcx>,
107+
alias: ty::AliasTy<'tcx>,
108+
other: ty::Term<'tcx>,
109+
direction: ty::AliasRelationDirection,
110+
invert: Invert,
111+
) -> QueryResult<'tcx> {
112+
self.probe(|ecx| {
113+
ecx.normalizes_to_inner(param_env, alias, other, direction, invert)?;
114+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
115+
})
116+
}
117+
118+
fn normalizes_to_inner(
119+
&mut self,
120+
param_env: ty::ParamEnv<'tcx>,
121+
alias: ty::AliasTy<'tcx>,
122+
other: ty::Term<'tcx>,
123+
direction: ty::AliasRelationDirection,
124+
invert: Invert,
125+
) -> Result<(), NoSolution> {
126+
let other = match direction {
127+
// This is purely an optimization.
128+
ty::AliasRelationDirection::Equate => other,
129+
130+
ty::AliasRelationDirection::Subtype => {
131+
let fresh = self.next_term_infer_of_kind(other);
132+
let (sub, sup) = match invert {
133+
Invert::No => (fresh, other),
134+
Invert::Yes => (other, fresh),
135+
};
136+
self.sub(param_env, sub, sup)?;
137+
fresh
138+
}
139+
};
140+
self.add_goal(Goal::new(
141+
self.tcx(),
142+
param_env,
143+
ty::Binder::dummy(ty::ProjectionPredicate { projection_ty: alias, term: other }),
144+
));
145+
146+
Ok(())
147+
}
148+
149+
fn assemble_subst_relate_candidate(
150+
&mut self,
151+
param_env: ty::ParamEnv<'tcx>,
152+
alias_lhs: ty::AliasTy<'tcx>,
153+
alias_rhs: ty::AliasTy<'tcx>,
154+
direction: ty::AliasRelationDirection,
155+
) -> QueryResult<'tcx> {
156+
self.probe(|ecx| {
157+
match direction {
158+
ty::AliasRelationDirection::Equate => {
159+
ecx.eq(param_env, alias_lhs, alias_rhs)?;
160+
}
161+
ty::AliasRelationDirection::Subtype => {
162+
ecx.sub(param_env, alias_lhs, alias_rhs)?;
163+
}
164+
}
165+
166+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
167+
})
168+
}
169+
170+
fn assemble_bidirectional_normalizes_to_candidate(
171+
&mut self,
172+
param_env: ty::ParamEnv<'tcx>,
173+
lhs: ty::Term<'tcx>,
174+
rhs: ty::Term<'tcx>,
175+
direction: ty::AliasRelationDirection,
176+
) -> QueryResult<'tcx> {
177+
self.probe(|ecx| {
178+
ecx.normalizes_to_inner(
179+
param_env,
180+
lhs.to_alias_ty(ecx.tcx()).unwrap(),
181+
rhs,
182+
direction,
183+
Invert::No,
184+
)?;
185+
ecx.normalizes_to_inner(
186+
param_env,
187+
rhs.to_alias_ty(ecx.tcx()).unwrap(),
188+
lhs,
189+
direction,
190+
Invert::Yes,
191+
)?;
192+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
193+
})
194+
}
195+
}

compiler/rustc_trait_selection/src/solve/mod.rs

+1-136
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use rustc_middle::ty::{
2020
CoercePredicate, RegionOutlivesPredicate, SubtypePredicate, TypeOutlivesPredicate,
2121
};
2222

23+
mod alias_relate;
2324
mod assembly;
2425
mod canonicalize;
2526
mod eval_ctxt;
@@ -154,142 +155,6 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
154155
}
155156
}
156157

157-
#[instrument(level = "debug", skip(self), ret)]
158-
fn compute_alias_relate_goal(
159-
&mut self,
160-
goal: Goal<'tcx, (ty::Term<'tcx>, ty::Term<'tcx>, ty::AliasRelationDirection)>,
161-
) -> QueryResult<'tcx> {
162-
let tcx = self.tcx();
163-
// We may need to invert the alias relation direction if dealing an alias on the RHS.
164-
#[derive(Debug)]
165-
enum Invert {
166-
No,
167-
Yes,
168-
}
169-
let evaluate_normalizes_to =
170-
|ecx: &mut EvalCtxt<'_, 'tcx>, alias, other, direction, invert| {
171-
let span = tracing::span!(
172-
tracing::Level::DEBUG,
173-
"compute_alias_relate_goal(evaluate_normalizes_to)",
174-
?alias,
175-
?other,
176-
?direction,
177-
?invert
178-
);
179-
let _enter = span.enter();
180-
let result = ecx.probe(|ecx| {
181-
let other = match direction {
182-
// This is purely an optimization.
183-
ty::AliasRelationDirection::Equate => other,
184-
185-
ty::AliasRelationDirection::Subtype => {
186-
let fresh = ecx.next_term_infer_of_kind(other);
187-
let (sub, sup) = match invert {
188-
Invert::No => (fresh, other),
189-
Invert::Yes => (other, fresh),
190-
};
191-
ecx.sub(goal.param_env, sub, sup)?;
192-
fresh
193-
}
194-
};
195-
ecx.add_goal(goal.with(
196-
tcx,
197-
ty::Binder::dummy(ty::ProjectionPredicate {
198-
projection_ty: alias,
199-
term: other,
200-
}),
201-
));
202-
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
203-
});
204-
debug!(?result);
205-
result
206-
};
207-
208-
let (lhs, rhs, direction) = goal.predicate;
209-
210-
if lhs.is_infer() || rhs.is_infer() {
211-
bug!(
212-
"`AliasRelate` goal with an infer var on lhs or rhs which should have been instantiated"
213-
);
214-
}
215-
216-
match (lhs.to_alias_ty(tcx), rhs.to_alias_ty(tcx)) {
217-
(None, None) => bug!("`AliasRelate` goal without an alias on either lhs or rhs"),
218-
219-
// RHS is not a projection, only way this is true is if LHS normalizes-to RHS
220-
(Some(alias_lhs), None) => {
221-
evaluate_normalizes_to(self, alias_lhs, rhs, direction, Invert::No)
222-
}
223-
224-
// LHS is not a projection, only way this is true is if RHS normalizes-to LHS
225-
(None, Some(alias_rhs)) => {
226-
evaluate_normalizes_to(self, alias_rhs, lhs, direction, Invert::Yes)
227-
}
228-
229-
(Some(alias_lhs), Some(alias_rhs)) => {
230-
debug!("both sides are aliases");
231-
232-
let mut candidates = Vec::new();
233-
// LHS normalizes-to RHS
234-
candidates.extend(evaluate_normalizes_to(
235-
self,
236-
alias_lhs,
237-
rhs,
238-
direction,
239-
Invert::No,
240-
));
241-
// RHS normalizes-to RHS
242-
candidates.extend(evaluate_normalizes_to(
243-
self,
244-
alias_rhs,
245-
lhs,
246-
direction,
247-
Invert::Yes,
248-
));
249-
// Relate via substs
250-
let subst_relate_response = self.probe(|ecx| {
251-
let span = tracing::span!(
252-
tracing::Level::DEBUG,
253-
"compute_alias_relate_goal(relate_via_substs)",
254-
?alias_lhs,
255-
?alias_rhs,
256-
?direction
257-
);
258-
let _enter = span.enter();
259-
260-
match direction {
261-
ty::AliasRelationDirection::Equate => {
262-
ecx.eq(goal.param_env, alias_lhs, alias_rhs)?;
263-
}
264-
ty::AliasRelationDirection::Subtype => {
265-
ecx.sub(goal.param_env, alias_lhs, alias_rhs)?;
266-
}
267-
}
268-
269-
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
270-
});
271-
candidates.extend(subst_relate_response);
272-
debug!(?candidates);
273-
274-
if let Some(merged) = self.try_merge_responses(&candidates) {
275-
Ok(merged)
276-
} else {
277-
// When relating two aliases and we have ambiguity, we prefer
278-
// relating the generic arguments of the aliases over normalizing
279-
// them. This is necessary for inference during typeck.
280-
//
281-
// As this is incomplete, we must not do so during coherence.
282-
match (self.solver_mode(), subst_relate_response) {
283-
(SolverMode::Normal, Ok(response)) => Ok(response),
284-
(SolverMode::Normal, Err(NoSolution)) | (SolverMode::Coherence, _) => {
285-
self.flounder(&candidates)
286-
}
287-
}
288-
}
289-
}
290-
}
291-
}
292-
293158
#[instrument(level = "debug", skip(self), ret)]
294159
fn compute_const_arg_has_type_goal(
295160
&mut self,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// compile-flags: -Ztrait-solver=next
2+
// check-pass
3+
4+
#![feature(type_alias_impl_trait)]
5+
6+
// Similar to tests/ui/traits/new-solver/tait-eq-proj.rs
7+
// but check the alias-sub relation in the other direction.
8+
9+
type Tait = impl Iterator<Item = impl Sized>;
10+
11+
fn mk<T>() -> T { todo!() }
12+
13+
fn a() {
14+
let x: Tait = mk();
15+
let mut array = mk();
16+
let mut z = IntoIterator::into_iter(array);
17+
z = x;
18+
array = [0i32; 32];
19+
}
20+
21+
fn main() {}

0 commit comments

Comments
 (0)