Skip to content

Commit c53125f

Browse files
Use a proof tree visitor to refine the Obligation for error reporting
1 parent ded5d6a commit c53125f

9 files changed

+201
-25
lines changed

compiler/rustc_trait_selection/src/solve/fulfill.rs

Lines changed: 159 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
use std::mem;
2+
use std::ops::ControlFlow;
23

34
use rustc_infer::infer::InferCtxt;
4-
use rustc_infer::traits::solve::MaybeCause;
5+
use rustc_infer::traits::query::NoSolution;
6+
use rustc_infer::traits::solve::inspect::ProbeKind;
7+
use rustc_infer::traits::solve::{CandidateSource, GoalSource, MaybeCause};
58
use rustc_infer::traits::{
6-
query::NoSolution, FulfillmentError, FulfillmentErrorCode, MismatchedProjectionTypes,
9+
self, FulfillmentError, FulfillmentErrorCode, MismatchedProjectionTypes, Obligation,
710
PredicateObligation, SelectionError, TraitEngine,
811
};
912
use rustc_middle::ty;
1013
use rustc_middle::ty::error::{ExpectedFound, TypeError};
1114

1215
use super::eval_ctxt::GenerateProofTree;
16+
use super::inspect::{ProofTreeInferCtxtExt, ProofTreeVisitor};
1317
use super::{Certainty, InferCtxtEvalExt};
1418

1519
/// A trait engine using the new trait solver.
@@ -133,9 +137,9 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
133137
.collect();
134138

135139
errors.extend(self.obligations.overflowed.drain(..).map(|obligation| FulfillmentError {
136-
root_obligation: obligation.clone(),
140+
obligation: find_best_leaf_obligation(infcx, &obligation),
137141
code: FulfillmentErrorCode::Ambiguity { overflow: Some(true) },
138-
obligation,
142+
root_obligation: obligation,
139143
}));
140144

141145
errors
@@ -234,7 +238,12 @@ fn fulfillment_error_for_no_solution<'tcx>(
234238
bug!("unexpected goal: {obligation:?}")
235239
}
236240
};
237-
FulfillmentError { root_obligation: obligation.clone(), code, obligation }
241+
242+
FulfillmentError {
243+
obligation: find_best_leaf_obligation(infcx, &obligation),
244+
code,
245+
root_obligation: obligation,
246+
}
238247
}
239248

240249
fn fulfillment_error_for_stalled<'tcx>(
@@ -258,5 +267,149 @@ fn fulfillment_error_for_stalled<'tcx>(
258267
}
259268
});
260269

261-
FulfillmentError { obligation: obligation.clone(), code, root_obligation: obligation }
270+
FulfillmentError {
271+
obligation: find_best_leaf_obligation(infcx, &obligation),
272+
code,
273+
root_obligation: obligation,
274+
}
275+
}
276+
277+
struct BestObligation<'tcx> {
278+
parent_kind: Option<ProbeKind<'tcx>>,
279+
parent_obligation: PredicateObligation<'tcx>,
280+
impl_where_bound_count: usize,
281+
}
282+
283+
impl<'tcx> BestObligation<'tcx> {
284+
fn with_derived_obligation(
285+
&mut self,
286+
new_kind: ProbeKind<'tcx>,
287+
derive_obligation: impl FnOnce(&mut Self, ProbeKind<'tcx>) -> PredicateObligation<'tcx>,
288+
and_then: impl FnOnce(&mut Self) -> <Self as ProofTreeVisitor<'tcx>>::Result,
289+
) -> <Self as ProofTreeVisitor<'tcx>>::Result {
290+
let mut old_obligation = None;
291+
let old_kind = self.parent_kind.replace(new_kind);
292+
if let Some(old_kind) = old_kind {
293+
let new_obligation = derive_obligation(self, old_kind);
294+
old_obligation = Some(std::mem::replace(&mut self.parent_obligation, new_obligation));
295+
}
296+
let old_impl_where_bound_count = std::mem::replace(&mut self.impl_where_bound_count, 0);
297+
298+
let res = and_then(self);
299+
300+
if let Some(old_obligation) = old_obligation {
301+
self.parent_obligation = old_obligation;
302+
}
303+
self.parent_kind = old_kind;
304+
self.impl_where_bound_count = old_impl_where_bound_count;
305+
306+
res
307+
}
308+
}
309+
310+
impl<'tcx> ProofTreeVisitor<'tcx> for BestObligation<'tcx> {
311+
type Result = ControlFlow<PredicateObligation<'tcx>>;
312+
313+
fn span(&self) -> rustc_span::Span {
314+
self.parent_obligation.cause.span
315+
}
316+
317+
fn visit_goal(&mut self, goal: &super::inspect::InspectGoal<'_, 'tcx>) -> Self::Result {
318+
if matches!(goal.source(), GoalSource::ImplWhereBound) {
319+
self.impl_where_bound_count += 1;
320+
}
321+
322+
if goal.result().is_ok() {
323+
return ControlFlow::Continue(());
324+
}
325+
326+
let candidates = goal.candidates();
327+
// FIXME: We should try to throw out the candidates that are definitely
328+
// not worthwhile, such as param-env and impl candidates whose headers
329+
// won't even unify. We already do this with deep-reject for impls, but
330+
// we shouldn't rely on this for diagnostic correctness.
331+
let [candidate] = candidates.as_slice() else {
332+
return ControlFlow::Break(self.parent_obligation.clone());
333+
};
334+
335+
// FIXME: Could we extract a trait ref from a projection here too?
336+
// FIXME: Also, what about considering >1 layer up the stack? May be necessary
337+
// for normalizes-to.
338+
let Some(parent_trait_pred) = self.parent_obligation.predicate.to_opt_poly_trait_pred()
339+
else {
340+
return ControlFlow::Break(self.parent_obligation.clone());
341+
};
342+
343+
self.with_derived_obligation(
344+
candidate.kind(),
345+
|self_, parent_kind| {
346+
let mut cause = self_.parent_obligation.cause.clone();
347+
cause = match (parent_kind, goal.source()) {
348+
(
349+
ProbeKind::TraitCandidate {
350+
source: CandidateSource::Impl(impl_def_id),
351+
result: _,
352+
},
353+
GoalSource::ImplWhereBound,
354+
) => {
355+
let idx = self_.impl_where_bound_count - 1;
356+
if let Some((_, span)) = goal
357+
.infcx()
358+
.tcx
359+
.predicates_of(impl_def_id)
360+
.instantiate_identity(goal.infcx().tcx)
361+
.iter()
362+
.nth(idx)
363+
{
364+
cause.derived_cause(parent_trait_pred, |derived| {
365+
traits::ImplDerivedObligation(Box::new(
366+
traits::ImplDerivedObligationCause {
367+
derived,
368+
impl_or_alias_def_id: impl_def_id,
369+
impl_def_predicate_index: Some(idx),
370+
span,
371+
},
372+
))
373+
})
374+
} else {
375+
cause
376+
}
377+
}
378+
(_, GoalSource::ImplWhereBound) => {
379+
cause.derived_cause(parent_trait_pred, traits::BuiltinDerivedObligation)
380+
}
381+
_ => cause,
382+
};
383+
384+
Obligation {
385+
cause,
386+
param_env: goal.goal().param_env,
387+
predicate: goal.goal().predicate,
388+
recursion_depth: self_.parent_obligation.recursion_depth + 1,
389+
}
390+
},
391+
|self_| {
392+
candidate.visit_nested_no_probe(self_)?;
393+
ControlFlow::Break(self_.parent_obligation.clone())
394+
},
395+
)
396+
}
397+
}
398+
399+
fn find_best_leaf_obligation<'tcx>(
400+
infcx: &InferCtxt<'tcx>,
401+
obligation: &PredicateObligation<'tcx>,
402+
) -> PredicateObligation<'tcx> {
403+
let obligation = infcx.resolve_vars_if_possible(obligation.clone());
404+
infcx
405+
.visit_proof_tree(
406+
obligation.clone().into(),
407+
&mut BestObligation {
408+
parent_kind: None,
409+
parent_obligation: obligation.clone(),
410+
impl_where_bound_count: 0,
411+
},
412+
)
413+
.break_value()
414+
.unwrap_or(obligation)
262415
}

tests/ui/for/issue-20605.next.stderr

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ error[E0277]: `dyn Iterator<Item = &'a mut u8>` is not an iterator
22
--> $DIR/issue-20605.rs:6:17
33
|
44
LL | for item in *things { *item = 0 }
5-
| ^^^^^^^ `dyn Iterator<Item = &'a mut u8>` is not an iterator
5+
| ^^^^^^^ the trait `IntoIterator` is not implemented for `dyn Iterator<Item = &'a mut u8>`
66
|
7-
= help: the trait `IntoIterator` is not implemented for `dyn Iterator<Item = &'a mut u8>`
7+
= note: the trait bound `dyn Iterator<Item = &'a mut u8>: IntoIterator` is not satisfied
8+
= note: required for `dyn Iterator<Item = &'a mut u8>` to implement `IntoIterator`
9+
help: consider mutably borrowing here
10+
|
11+
LL | for item in &mut *things { *item = 0 }
12+
| ++++
813

914
error: the type `<dyn Iterator<Item = &'a mut u8> as IntoIterator>::IntoIter` is not well-formed
1015
--> $DIR/issue-20605.rs:6:17

tests/ui/traits/next-solver/auto-with-drop_tracking_mir.fail.stderr

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
1-
error[E0277]: `impl Future<Output = ()>` cannot be sent between threads safely
1+
error: future cannot be sent between threads safely
22
--> $DIR/auto-with-drop_tracking_mir.rs:25:13
33
|
44
LL | is_send(foo());
5-
| ------- ^^^^^ `impl Future<Output = ()>` cannot be sent between threads safely
6-
| |
7-
| required by a bound introduced by this call
5+
| ^^^^^ future returned by `foo` is not `Send`
86
|
9-
= help: the trait `Send` is not implemented for `impl Future<Output = ()>`
7+
= help: the trait `Sync` is not implemented for `impl Future<Output = ()>`, which is required by `impl Future<Output = ()>: Send`
8+
note: future is not `Send` as this value is used across an await
9+
--> $DIR/auto-with-drop_tracking_mir.rs:16:11
10+
|
11+
LL | let x = &NotSync;
12+
| - has type `&NotSync` which is not `Send`
13+
LL | bar().await;
14+
| ^^^^^ await occurs here, with `x` maybe used later
1015
note: required by a bound in `is_send`
1116
--> $DIR/auto-with-drop_tracking_mir.rs:24:24
1217
|
1318
LL | fn is_send(_: impl Send) {}
1419
| ^^^^ required by this bound in `is_send`
20+
help: consider dereferencing here
21+
|
22+
LL | is_send(*foo());
23+
| +
1524

1625
error: aborting due to 1 previous error
1726

18-
For more information about this error, try `rustc --explain E0277`.

tests/ui/traits/next-solver/auto-with-drop_tracking_mir.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ async fn bar() {}
2323
fn main() {
2424
fn is_send(_: impl Send) {}
2525
is_send(foo());
26-
//[fail]~^ ERROR `impl Future<Output = ()>` cannot be sent between threads safely
26+
//[fail]~^ ERROR future cannot be sent between threads safely
2727
}

tests/ui/traits/next-solver/builtin-fn-must-return-sized.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ fn foo<F: Fn<T>, T: Tuple>(f: Option<F>, t: T) {
1313

1414
fn main() {
1515
foo::<fn() -> str, _>(None, ());
16-
//~^ expected a `Fn<_>` closure, found `fn() -> str`
16+
//~^ the size for values of type `str` cannot be known at compilation time
1717
}

tests/ui/traits/next-solver/builtin-fn-must-return-sized.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
error[E0277]: expected a `Fn<_>` closure, found `fn() -> str`
1+
error[E0277]: the size for values of type `str` cannot be known at compilation time
22
--> $DIR/builtin-fn-must-return-sized.rs:15:11
33
|
44
LL | foo::<fn() -> str, _>(None, ());
5-
| ^^^^^^^^^^^ expected an `Fn<_>` closure, found `fn() -> str`
5+
| ^^^^^^^^^^^ doesn't have a size known at compile-time
66
|
7-
= help: the trait `Fn<_>` is not implemented for `fn() -> str`
7+
= help: the trait `Sized` is not implemented for `str`
88
note: required by a bound in `foo`
99
--> $DIR/builtin-fn-must-return-sized.rs:10:11
1010
|

tests/ui/traits/next-solver/cycles/coinduction/incompleteness-unstable-result.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ LL | impls_trait::<A<X>, _, _, _>();
55
| ^^^^ the trait `Trait<_, _, _>` is not implemented for `A<X>`
66
|
77
= help: the trait `Trait<U, V, D>` is implemented for `A<T>`
8+
note: required for `A<X>` to implement `Trait<_, _, _>`
9+
--> $DIR/incompleteness-unstable-result.rs:32:50
10+
|
11+
LL | impl<T: ?Sized, U: ?Sized, V: ?Sized, D: ?Sized> Trait<U, V, D> for A<T>
12+
| ^^^^^^^^^^^^^^ ^^^^
13+
...
14+
LL | A<T>: Trait<U, D, V>,
15+
| -------------- unsatisfied trait bound introduced here
16+
= note: 9 redundant requirements hidden
17+
= note: required for `A<X>` to implement `Trait<_, _, _>`
818
note: required by a bound in `impls_trait`
919
--> $DIR/incompleteness-unstable-result.rs:51:28
1020
|

tests/ui/traits/next-solver/object-unsafety.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ fn copy<U: Setup + ?Sized>(from: &U::From) -> U::From {
1111
pub fn copy_any<T>(t: &T) -> T {
1212
copy::<dyn Setup<From=T>>(t)
1313
//~^ ERROR the type `&<dyn Setup<From = T> as Setup>::From` is not well-formed
14-
//~| ERROR the trait bound `dyn Setup<From = T>: Setup` is not satisfied
1514
//~| ERROR mismatched types
1615
//~| ERROR the type `<dyn Setup<From = T> as Setup>::From` is not well-formed
16+
//~| ERROR the trait bound `T: Copy` is not satisfied
1717

1818
// FIXME(-Znext-solver): These error messages are horrible and some of them
1919
// are even simple fallout from previous error.

tests/ui/traits/next-solver/object-unsafety.stderr

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
error[E0277]: the trait bound `dyn Setup<From = T>: Setup` is not satisfied
1+
error[E0277]: the trait bound `T: Copy` is not satisfied
22
--> $DIR/object-unsafety.rs:12:12
33
|
44
LL | copy::<dyn Setup<From=T>>(t)
5-
| ^^^^^^^^^^^^^^^^^ the trait `Setup` is not implemented for `dyn Setup<From = T>`
5+
| ^^^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `T`
66
|
77
note: required by a bound in `copy`
88
--> $DIR/object-unsafety.rs:7:12
99
|
1010
LL | fn copy<U: Setup + ?Sized>(from: &U::From) -> U::From {
1111
| ^^^^^ required by this bound in `copy`
12-
help: consider introducing a `where` clause, but there might be an alternative better way to express this requirement
12+
help: consider restricting type parameter `T`
1313
|
14-
LL | pub fn copy_any<T>(t: &T) -> T where dyn Setup<From = T>: Setup {
15-
| ++++++++++++++++++++++++++++++++
14+
LL | pub fn copy_any<T: std::marker::Copy>(t: &T) -> T {
15+
| +++++++++++++++++++
1616

1717
error: the type `&<dyn Setup<From = T> as Setup>::From` is not well-formed
1818
--> $DIR/object-unsafety.rs:12:31

0 commit comments

Comments
 (0)