Skip to content

Commit da969d4

Browse files
committed
fix NormalizesTo proof tree issue
1 parent 20aa2d8 commit da969d4

File tree

4 files changed

+177
-52
lines changed

4 files changed

+177
-52
lines changed

compiler/rustc_trait_selection/src/solve/inspect/analyse.rs

Lines changed: 118 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use rustc_ast_ir::visit::VisitorResult;
1414
use rustc_infer::infer::resolve::EagerResolver;
1515
use rustc_infer::infer::type_variable::TypeVariableOrigin;
1616
use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk};
17+
use rustc_infer::traits::{TraitEngine, TraitEngineExt};
1718
use rustc_macros::extension;
1819
use rustc_middle::infer::unify_key::ConstVariableOrigin;
1920
use rustc_middle::traits::query::NoSolution;
@@ -22,9 +23,10 @@ use rustc_middle::traits::solve::{Certainty, Goal};
2223
use rustc_middle::traits::ObligationCause;
2324
use rustc_middle::ty;
2425
use rustc_middle::ty::TypeFoldable;
25-
use rustc_span::Span;
26+
use rustc_span::{Span, DUMMY_SP};
2627

2728
use crate::solve::eval_ctxt::canonical;
29+
use crate::solve::FulfillmentCtxt;
2830
use crate::solve::{EvalCtxt, GoalEvaluationKind, GoalSource};
2931
use crate::solve::{GenerateProofTree, InferCtxtEvalExt};
3032

@@ -37,7 +39,52 @@ pub struct InspectGoal<'a, 'tcx> {
3739
depth: usize,
3840
orig_values: Vec<ty::GenericArg<'tcx>>,
3941
goal: Goal<'tcx, ty::Predicate<'tcx>>,
40-
evaluation: inspect::CanonicalGoalEvaluation<'tcx>,
42+
result: Result<Certainty, NoSolution>,
43+
evaluation_kind: inspect::CanonicalGoalEvaluationKind<'tcx>,
44+
/// The expected term of a `NormalizesTo` goal. It gets
45+
/// replaced with an unconstrained inference variable when
46+
/// computing `NormalizesTo` goals and we return the nested
47+
/// goals to the caller, who also equates the actual term
48+
/// with the expected.
49+
///
50+
/// This is an implementation detail of the trait solver and
51+
/// not something we want to leak to users. We therefore
52+
/// treat `NormalizesTo` goals as if they apply the expected
53+
/// type at the end of each candidate.
54+
normalizes_to_term_hack: Option<NormalizesToTermHack<'tcx>>,
55+
}
56+
57+
#[derive(Copy, Clone)]
58+
struct NormalizesToTermHack<'tcx> {
59+
term: ty::Term<'tcx>,
60+
unconstrained_term: ty::Term<'tcx>,
61+
}
62+
63+
impl<'tcx> NormalizesToTermHack<'tcx> {
64+
fn relate(
65+
self,
66+
infcx: &InferCtxt<'tcx>,
67+
span: Span,
68+
param_env: ty::ParamEnv<'tcx>,
69+
) -> Result<Certainty, NoSolution> {
70+
infcx
71+
.at(&ObligationCause::dummy_with_span(span), param_env)
72+
.eq(DefineOpaqueTypes::Yes, self.term, self.unconstrained_term)
73+
.map_err(|_| NoSolution)
74+
.and_then(|InferOk { value: (), obligations }| {
75+
let mut fulfill_cx = FulfillmentCtxt::new(infcx);
76+
fulfill_cx.register_predicate_obligations(infcx, obligations);
77+
if fulfill_cx.select_where_possible(infcx).is_empty() {
78+
if fulfill_cx.pending_obligations().is_empty() {
79+
Ok(Certainty::Yes)
80+
} else {
81+
Ok(Certainty::AMBIGUOUS)
82+
}
83+
} else {
84+
Err(NoSolution)
85+
}
86+
})
87+
}
4188
}
4289

4390
pub struct InspectCandidate<'a, 'tcx> {
@@ -115,42 +162,47 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> {
115162
self.final_state,
116163
);
117164

165+
if let Some(term_hack) = self.goal.normalizes_to_term_hack {
166+
// FIXME: We ignore the expected term of `NormalizesTo` goals
167+
// when computing the result of its candidates. This is
168+
// scuffed.
169+
let _ = term_hack.relate(infcx, span, param_env);
170+
}
171+
118172
instantiated_goals
119173
.into_iter()
120-
.map(|goal| {
121-
let proof_tree = match goal.predicate.kind().no_bound_vars() {
122-
Some(ty::PredicateKind::NormalizesTo(ty::NormalizesTo { alias, term })) => {
123-
let unconstrained_term = match term.unpack() {
124-
ty::TermKind::Ty(_) => infcx
125-
.next_ty_var(TypeVariableOrigin { param_def_id: None, span })
126-
.into(),
127-
ty::TermKind::Const(ct) => infcx
128-
.next_const_var(
129-
ct.ty(),
130-
ConstVariableOrigin { param_def_id: None, span },
131-
)
132-
.into(),
133-
};
134-
let goal = goal
135-
.with(infcx.tcx, ty::NormalizesTo { alias, term: unconstrained_term });
136-
let proof_tree =
137-
EvalCtxt::enter_root(infcx, GenerateProofTree::Yes, |ecx| {
138-
ecx.evaluate_goal_raw(
139-
GoalEvaluationKind::Root,
140-
GoalSource::Misc,
141-
goal,
142-
)
143-
})
144-
.1;
145-
let InferOk { value: (), obligations: _ } = infcx
146-
.at(&ObligationCause::dummy_with_span(span), param_env)
147-
.eq(DefineOpaqueTypes::Yes, term, unconstrained_term)
148-
.unwrap();
149-
proof_tree
150-
}
151-
_ => infcx.evaluate_root_goal(goal, GenerateProofTree::Yes).1,
152-
};
153-
InspectGoal::new(infcx, self.goal.depth + 1, proof_tree.unwrap())
174+
.map(|goal| match goal.predicate.kind().no_bound_vars() {
175+
Some(ty::PredicateKind::NormalizesTo(ty::NormalizesTo { alias, term })) => {
176+
let unconstrained_term = match term.unpack() {
177+
ty::TermKind::Ty(_) => infcx
178+
.next_ty_var(TypeVariableOrigin { param_def_id: None, span })
179+
.into(),
180+
ty::TermKind::Const(ct) => infcx
181+
.next_const_var(
182+
ct.ty(),
183+
ConstVariableOrigin { param_def_id: None, span },
184+
)
185+
.into(),
186+
};
187+
let goal =
188+
goal.with(infcx.tcx, ty::NormalizesTo { alias, term: unconstrained_term });
189+
let proof_tree = EvalCtxt::enter_root(infcx, GenerateProofTree::Yes, |ecx| {
190+
ecx.evaluate_goal_raw(GoalEvaluationKind::Root, GoalSource::Misc, goal)
191+
})
192+
.1;
193+
InspectGoal::new(
194+
infcx,
195+
self.goal.depth + 1,
196+
proof_tree.unwrap(),
197+
Some(NormalizesToTermHack { term, unconstrained_term }),
198+
)
199+
}
200+
_ => InspectGoal::new(
201+
infcx,
202+
self.goal.depth + 1,
203+
infcx.evaluate_root_goal(goal, GenerateProofTree::Yes).1.unwrap(),
204+
None,
205+
),
154206
})
155207
.collect()
156208
}
@@ -172,7 +224,7 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
172224
}
173225

174226
pub fn result(&self) -> Result<Certainty, NoSolution> {
175-
self.evaluation.result.map(|c| c.value.certainty)
227+
self.result
176228
}
177229

178230
fn candidates_recur(
@@ -229,11 +281,11 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
229281

230282
pub fn candidates(&'a self) -> Vec<InspectCandidate<'a, 'tcx>> {
231283
let mut candidates = vec![];
232-
let last_eval_step = match self.evaluation.kind {
284+
let last_eval_step = match self.evaluation_kind {
233285
inspect::CanonicalGoalEvaluationKind::Overflow
234286
| inspect::CanonicalGoalEvaluationKind::CycleInStack
235287
| inspect::CanonicalGoalEvaluationKind::ProvisionalCacheHit => {
236-
warn!("unexpected root evaluation: {:?}", self.evaluation);
288+
warn!("unexpected root evaluation: {:?}", self.evaluation_kind);
237289
return vec![];
238290
}
239291
inspect::CanonicalGoalEvaluationKind::Evaluation { revisions } => {
@@ -262,17 +314,33 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
262314
candidates.pop().filter(|_| candidates.is_empty())
263315
}
264316

265-
fn new(infcx: &'a InferCtxt<'tcx>, depth: usize, root: inspect::GoalEvaluation<'tcx>) -> Self {
317+
fn new(
318+
infcx: &'a InferCtxt<'tcx>,
319+
depth: usize,
320+
root: inspect::GoalEvaluation<'tcx>,
321+
normalizes_to_term_hack: Option<NormalizesToTermHack<'tcx>>,
322+
) -> Self {
266323
let inspect::GoalEvaluation { uncanonicalized_goal, kind, evaluation } = root;
267-
match kind {
268-
inspect::GoalEvaluationKind::Root { orig_values } => InspectGoal {
269-
infcx,
270-
depth,
271-
orig_values,
272-
goal: uncanonicalized_goal.fold_with(&mut EagerResolver::new(infcx)),
273-
evaluation,
274-
},
275-
inspect::GoalEvaluationKind::Nested { .. } => unreachable!(),
324+
let inspect::GoalEvaluationKind::Root { orig_values } = kind else { unreachable!() };
325+
326+
let result = evaluation.result.and_then(|ok| {
327+
if let Some(term_hack) = normalizes_to_term_hack {
328+
infcx
329+
.probe(|_| term_hack.relate(infcx, DUMMY_SP, uncanonicalized_goal.param_env))
330+
.map(|certainty| ok.value.certainty.unify_with(certainty))
331+
} else {
332+
Ok(ok.value.certainty)
333+
}
334+
});
335+
336+
InspectGoal {
337+
infcx,
338+
depth,
339+
orig_values,
340+
goal: uncanonicalized_goal.fold_with(&mut EagerResolver::new(infcx)),
341+
result,
342+
evaluation_kind: evaluation.kind,
343+
normalizes_to_term_hack,
276344
}
277345
}
278346
}
@@ -299,6 +367,6 @@ impl<'tcx> InferCtxt<'tcx> {
299367
) -> V::Result {
300368
let (_, proof_tree) = self.evaluate_root_goal(goal, GenerateProofTree::Yes);
301369
let proof_tree = proof_tree.unwrap();
302-
visitor.visit_goal(&InspectGoal::new(self, 0, proof_tree))
370+
visitor.visit_goal(&InspectGoal::new(self, 0, proof_tree, None))
303371
}
304372
}

tests/ui/specialization/specialization-overlap-projection.stderr renamed to tests/ui/specialization/specialization-overlap-projection.current.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
2-
--> $DIR/specialization-overlap-projection.rs:7:12
2+
--> $DIR/specialization-overlap-projection.rs:10:12
33
|
44
LL | #![feature(specialization)]
55
| ^^^^^^^^^^^^^^
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
2+
--> $DIR/specialization-overlap-projection.rs:10:12
3+
|
4+
LL | #![feature(specialization)]
5+
| ^^^^^^^^^^^^^^
6+
|
7+
= note: see issue #31844 <https://github.com/rust-lang/rust/issues/31844> for more information
8+
= help: consider using `min_specialization` instead, which is more stable and complete
9+
= note: `#[warn(incomplete_features)]` on by default
10+
11+
error[E0119]: conflicting implementations of trait `Foo` for type `u32`
12+
--> $DIR/specialization-overlap-projection.rs:28:1
13+
|
14+
LL | impl Foo for u32 {}
15+
| ---------------- first implementation here
16+
LL | impl Foo for <u8 as Assoc>::Output {}
17+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `u32`
18+
19+
error[E0119]: conflicting implementations of trait `Foo` for type `u32`
20+
--> $DIR/specialization-overlap-projection.rs:30:1
21+
|
22+
LL | impl Foo for u32 {}
23+
| ---------------- first implementation here
24+
...
25+
LL | impl Foo for <u16 as Assoc>::Output {}
26+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `u32`
27+
28+
error[E0282]: type annotations needed
29+
--> $DIR/specialization-overlap-projection.rs:17:27
30+
|
31+
LL | default type Output = bool;
32+
| ^^^^ cannot infer type for associated type `<T as Assoc>::Output`
33+
34+
error[E0282]: type annotations needed
35+
--> $DIR/specialization-overlap-projection.rs:21:35
36+
|
37+
LL | impl Assoc for u8 { type Output = u8; }
38+
| ^^ cannot infer type for associated type `<u8 as Assoc>::Output`
39+
40+
error[E0282]: type annotations needed
41+
--> $DIR/specialization-overlap-projection.rs:23:36
42+
|
43+
LL | impl Assoc for u16 { type Output = u16; }
44+
| ^^^ cannot infer type for associated type `<u16 as Assoc>::Output`
45+
46+
error: aborting due to 5 previous errors; 1 warning emitted
47+
48+
Some errors have detailed explanations: E0119, E0282.
49+
For more information about an error, try `rustc --explain E0119`.

tests/ui/specialization/specialization-overlap-projection.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
//@ check-pass
1+
//@ revisions: current next
2+
//@ ignore-compare-mode-next-solver (explicit revisions)
3+
//@[next] compile-flags: -Znext-solver
4+
//@[current] check-pass
25

36
// Test that impls on projected self types can resolve overlap, even when the
47
// projections involve specialization, so long as the associated type is
@@ -12,14 +15,19 @@ trait Assoc {
1215

1316
impl<T> Assoc for T {
1417
default type Output = bool;
18+
//[next]~^ ERROR type annotations needed
1519
}
1620

1721
impl Assoc for u8 { type Output = u8; }
22+
//[next]~^ ERROR type annotations needed
1823
impl Assoc for u16 { type Output = u16; }
24+
//[next]~^ ERROR type annotations needed
1925

2026
trait Foo {}
2127
impl Foo for u32 {}
2228
impl Foo for <u8 as Assoc>::Output {}
29+
//[next]~^ ERROR conflicting implementations of trait `Foo` for type `u32`
2330
impl Foo for <u16 as Assoc>::Output {}
31+
//[next]~^ ERROR conflicting implementations of trait `Foo` for type `u32`
2432

2533
fn main() {}

0 commit comments

Comments
 (0)