Skip to content

Commit 800221b

Browse files
authored
Rollup merge of #106477 - Nathan-Fenner:nathanf/refined-error-span-trait-impl, r=compiler-errors
Refine error spans for "The trait bound `T: Trait` is not satisfied" when passing literal structs/tuples This PR adds a new heuristic which refines the error span reported for "`T: Trait` is not satisfied" errors, by "drilling down" into individual fields of structs/enums/tuples to point to the "problematic" value. Here's a self-contained example of the difference in error span: ```rs struct Burrito<Filling> { filling: Filling, } impl <Filling: Delicious> Delicious for Burrito<Filling> {} fn eat_delicious_food<Food: Delicious>(food: Food) {} fn will_type_error() { eat_delicious_food(Burrito { filling: Kale }); // ^~~~~~~~~~~~~~~~~~~~~~~~~ (before) The trait bound `Kale: Delicious` is not satisfied // ^~~~ (after) The trait bound `Kale: Delicious` is not satisfied } ``` (kale is fine, this is just a silly food-based example) Before this PR, the error span is identified as the entire argument to the generic function `eat_delicious_food`. However, since only `Kale` is the "problematic" part, we can point at it specifically. In particular, the primary error message itself mentions the missing `Kale: Delicious` trait bound, so it's much clearer if this part is called out explicitly. --- The _existing_ heuristic tries to label the right function argument in `point_at_arg_if_possible`. It goes something like this: - Look at the broken base trait `Food: Delicious` and find which generics it mentions (in this case, only `Food`) - Look at the parameter type definitions and find which of them mention `Filling` (in this case, only `food`) - If there is exactly one relevant parameter, label the corresponding argument with the error span, instead of the entire call This PR extends this heuristic by further refining the resulting expression span in the new `point_at_specific_expr_if_possible` function. For each `impl` in the (broken) chain, we apply the following strategy: The strategy to determine this span involves connecting information about our generic `impl` with information about our (struct) type and the (struct) literal expression: - Find the `impl` (`impl <Filling: Delicious> Delicious for Burrito<Filling>`) that links our obligation (`Kale: Delicious`) with the parent obligation (`Burrito<Kale>: Delicious`) - Find the "original" predicate constraint in the impl (`Filling: Delicious`) which produced our obligation. - Find all of the generics that are mentioned in the predicate (`Filling`). - Examine the `Self` type in the `impl`, and see which of its type argument(s) mention any of those generics. - Examing the definition for the `Self` type, and identify (for each of its variants) if there's a unique field which uses those generic arguments. - If there is a unique field mentioning the "blameable" arguments, use that field for the error span. Before we do any of this logic, we recursively call `point_at_specific_expr_if_possible` on the parent obligation. Hence we refine the `expr` "outwards-in" and bail at the first kind of expression/impl we don't recognize. This function returns a `Result<&Expr, &Expr>` - either way, it returns the `Expr` whose span should be reported as an error. If it is `Ok`, then it means it refined successfull. If it is `Err`, then it may be only a partial success - but it cannot be refined even further. --- I added a new test file which exercises this new behavior. A few existing tests were affected, since their error spans are now different. In one case, this leads to a different code suggestion for the autofix - although the new suggestion isn't _wrong_, it is different from what used to be. This change doesn't create any new errors or remove any existing ones, it just adjusts the spans where they're presented. --- Some considerations: right now, this check occurs in addition to some similar logic in `adjust_fulfillment_error_for_expr_obligation` function, which tidies up various kinds of error spans (not just trait-fulfillment error). It's possible that this new code would be better integrated into that function (or another one) - but I haven't looked into this yet. Although this code only occurs when there's a type error, it's definitely not as efficient as possible. In particular, there are definitely some cases where it degrades to quadratic performance (e.g. for a trait `impl` with 100+ generic parameters or 100 levels deep nesting of generic types). I'm not sure if these are realistic enough to worry about optimizing yet. There's also still a lot of repetition in some of the logic, where the behavior for different types (namely, `struct` vs `enum` variant) is _similar_ but not the same. --- I think the biggest win here is better targeting for tuples; in particular, if you're using tuples + traits to express variadic-like functions, the compiler can't tell you which part of a tuple has the wrong type, since the span will cover the entire argument. This change allows the individual field in the tuple to be highlighted, as in this example: ``` // NEW LL | want(Wrapper { value: (3, q) }); | ---- ^ the trait `T3` is not implemented for `Q` // OLD LL | want(Wrapper { value: (3, q) }); | ---- ^~~~~~~~~~~~~~~~~~~~~~~~~ the trait `T3` is not implemented for `Q` ``` Especially with large tuples, the existing error spans are not very effective at quickly narrowing down the source of the problem.
2 parents 7ff69b4 + 99638a6 commit 800221b

File tree

14 files changed

+1120
-81
lines changed

14 files changed

+1120
-81
lines changed

compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs

+457
Large diffs are not rendered by default.

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

+42-42
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ use rustc_trait_selection::traits::{self, ObligationCauseCode, SelectionContext}
3434

3535
use std::iter;
3636
use std::mem;
37-
use std::ops::ControlFlow;
3837
use std::slice;
3938

39+
use std::ops::ControlFlow;
40+
4041
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
4142
pub(in super::super) fn check_casts(&mut self) {
4243
// don't hold the borrow to deferred_cast_checks while checking to avoid borrow checker errors
@@ -1843,7 +1844,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18431844
.into_iter()
18441845
.flatten()
18451846
{
1846-
if self.point_at_arg_if_possible(
1847+
if self.blame_specific_arg_if_possible(
18471848
error,
18481849
def_id,
18491850
param,
@@ -1873,7 +1874,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18731874
.into_iter()
18741875
.flatten()
18751876
{
1876-
if self.point_at_arg_if_possible(
1877+
if self.blame_specific_arg_if_possible(
18771878
error,
18781879
def_id,
18791880
param,
@@ -1898,16 +1899,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18981899
for param in
18991900
[param_to_point_at, fallback_param_to_point_at, self_param_to_point_at]
19001901
{
1901-
if let Some(param) = param
1902-
&& self.point_at_field_if_possible(
1903-
error,
1902+
if let Some(param) = param {
1903+
let refined_expr = self.point_at_field_if_possible(
19041904
def_id,
19051905
param,
19061906
variant_def_id,
19071907
fields,
1908-
)
1909-
{
1910-
return true;
1908+
);
1909+
1910+
match refined_expr {
1911+
None => {}
1912+
Some((refined_expr, _)) => {
1913+
error.obligation.cause.span = refined_expr
1914+
.span
1915+
.find_ancestor_in_same_ctxt(error.obligation.cause.span)
1916+
.unwrap_or(refined_expr.span);
1917+
return true;
1918+
}
1919+
}
19111920
}
19121921
}
19131922
}
@@ -1940,7 +1949,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
19401949
}
19411950
}
19421951

1943-
fn point_at_arg_if_possible(
1952+
/// - `blame_specific_*` means that the function will recursively traverse the expression,
1953+
/// looking for the most-specific-possible span to blame.
1954+
///
1955+
/// - `point_at_*` means that the function will only go "one level", pointing at the specific
1956+
/// expression mentioned.
1957+
///
1958+
/// `blame_specific_arg_if_possible` will find the most-specific expression anywhere inside
1959+
/// the provided function call expression, and mark it as responsible for the fullfillment
1960+
/// error.
1961+
fn blame_specific_arg_if_possible(
19441962
&self,
19451963
error: &mut traits::FulfillmentError<'tcx>,
19461964
def_id: DefId,
@@ -1959,13 +1977,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
19591977
.inputs()
19601978
.iter()
19611979
.enumerate()
1962-
.filter(|(_, ty)| find_param_in_ty(**ty, param_to_point_at))
1980+
.filter(|(_, ty)| Self::find_param_in_ty((**ty).into(), param_to_point_at))
19631981
.collect();
19641982
// If there's one field that references the given generic, great!
19651983
if let [(idx, _)] = args_referencing_param.as_slice()
19661984
&& let Some(arg) = receiver
19671985
.map_or(args.get(*idx), |rcvr| if *idx == 0 { Some(rcvr) } else { args.get(*idx - 1) }) {
1986+
19681987
error.obligation.cause.span = arg.span.find_ancestor_in_same_ctxt(error.obligation.cause.span).unwrap_or(arg.span);
1988+
1989+
if let hir::Node::Expr(arg_expr) = self.tcx.hir().get(arg.hir_id) {
1990+
// This is more specific than pointing at the entire argument.
1991+
self.blame_specific_expr_if_possible(error, arg_expr)
1992+
}
1993+
19691994
error.obligation.cause.map_code(|parent_code| {
19701995
ObligationCauseCode::FunctionArgumentObligation {
19711996
arg_hir_id: arg.hir_id,
@@ -1983,14 +2008,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
19832008
false
19842009
}
19852010

1986-
fn point_at_field_if_possible(
2011+
// FIXME: Make this private and move to mod adjust_fulfillment_errors
2012+
pub fn point_at_field_if_possible(
19872013
&self,
1988-
error: &mut traits::FulfillmentError<'tcx>,
19892014
def_id: DefId,
19902015
param_to_point_at: ty::GenericArg<'tcx>,
19912016
variant_def_id: DefId,
19922017
expr_fields: &[hir::ExprField<'tcx>],
1993-
) -> bool {
2018+
) -> Option<(&'tcx hir::Expr<'tcx>, Ty<'tcx>)> {
19942019
let def = self.tcx.adt_def(def_id);
19952020

19962021
let identity_substs = ty::InternalSubsts::identity_for_item(self.tcx, def_id);
@@ -2000,7 +2025,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
20002025
.iter()
20012026
.filter(|field| {
20022027
let field_ty = field.ty(self.tcx, identity_substs);
2003-
find_param_in_ty(field_ty, param_to_point_at)
2028+
Self::find_param_in_ty(field_ty.into(), param_to_point_at)
20042029
})
20052030
.collect();
20062031

@@ -2010,17 +2035,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
20102035
// same rules that check_expr_struct uses for macro hygiene.
20112036
if self.tcx.adjust_ident(expr_field.ident, variant_def_id) == field.ident(self.tcx)
20122037
{
2013-
error.obligation.cause.span = expr_field
2014-
.expr
2015-
.span
2016-
.find_ancestor_in_same_ctxt(error.obligation.cause.span)
2017-
.unwrap_or(expr_field.span);
2018-
return true;
2038+
return Some((expr_field.expr, self.tcx.type_of(field.did)));
20192039
}
20202040
}
20212041
}
20222042

2023-
false
2043+
None
20242044
}
20252045

20262046
fn point_at_path_if_possible(
@@ -2240,23 +2260,3 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
22402260
}
22412261
}
22422262
}
2243-
2244-
fn find_param_in_ty<'tcx>(ty: Ty<'tcx>, param_to_point_at: ty::GenericArg<'tcx>) -> bool {
2245-
let mut walk = ty.walk();
2246-
while let Some(arg) = walk.next() {
2247-
if arg == param_to_point_at {
2248-
return true;
2249-
} else if let ty::GenericArgKind::Type(ty) = arg.unpack()
2250-
&& let ty::Alias(ty::Projection, ..) = ty.kind()
2251-
{
2252-
// This logic may seem a bit strange, but typically when
2253-
// we have a projection type in a function signature, the
2254-
// argument that's being passed into that signature is
2255-
// not actually constraining that projection's substs in
2256-
// a meaningful way. So we skip it, and see improvements
2257-
// in some UI tests.
2258-
walk.skip_current_subtree();
2259-
}
2260-
}
2261-
false
2262-
}

compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod _impl;
2+
mod adjust_fulfillment_errors;
23
mod arg_matrix;
34
mod checks;
45
mod suggestions;

compiler/rustc_hir_typeck/src/method/probe.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1563,6 +1563,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
15631563
traits::ImplDerivedObligationCause {
15641564
derived,
15651565
impl_def_id,
1566+
impl_def_predicate_index: None,
15661567
span,
15671568
},
15681569
))

compiler/rustc_infer/src/traits/util.rs

+26-24
Original file line numberDiff line numberDiff line change
@@ -145,30 +145,32 @@ impl<'tcx> Elaborator<'tcx> {
145145
// Get predicates declared on the trait.
146146
let predicates = tcx.super_predicates_of(data.def_id());
147147

148-
let obligations = predicates.predicates.iter().map(|&(mut pred, span)| {
149-
// when parent predicate is non-const, elaborate it to non-const predicates.
150-
if data.constness == ty::BoundConstness::NotConst {
151-
pred = pred.without_const(tcx);
152-
}
153-
154-
let cause = obligation.cause.clone().derived_cause(
155-
bound_predicate.rebind(data),
156-
|derived| {
157-
traits::ImplDerivedObligation(Box::new(
158-
traits::ImplDerivedObligationCause {
159-
derived,
160-
impl_def_id: data.def_id(),
161-
span,
162-
},
163-
))
164-
},
165-
);
166-
predicate_obligation(
167-
pred.subst_supertrait(tcx, &bound_predicate.rebind(data.trait_ref)),
168-
obligation.param_env,
169-
cause,
170-
)
171-
});
148+
let obligations =
149+
predicates.predicates.iter().enumerate().map(|(index, &(mut pred, span))| {
150+
// when parent predicate is non-const, elaborate it to non-const predicates.
151+
if data.constness == ty::BoundConstness::NotConst {
152+
pred = pred.without_const(tcx);
153+
}
154+
155+
let cause = obligation.cause.clone().derived_cause(
156+
bound_predicate.rebind(data),
157+
|derived| {
158+
traits::ImplDerivedObligation(Box::new(
159+
traits::ImplDerivedObligationCause {
160+
derived,
161+
impl_def_id: data.def_id(),
162+
impl_def_predicate_index: Some(index),
163+
span,
164+
},
165+
))
166+
},
167+
);
168+
predicate_obligation(
169+
pred.subst_supertrait(tcx, &bound_predicate.rebind(data.trait_ref)),
170+
obligation.param_env,
171+
cause,
172+
)
173+
});
172174
debug!(?data, ?obligations, "super_predicates");
173175

174176
// Only keep those bounds that we haven't already seen.

compiler/rustc_middle/src/traits/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,8 @@ pub enum WellFormedLoc {
475475
pub struct ImplDerivedObligationCause<'tcx> {
476476
pub derived: DerivedObligationCause<'tcx>,
477477
pub impl_def_id: DefId,
478+
/// The index of the derived predicate in the parent impl's predicates.
479+
pub impl_def_predicate_index: Option<usize>,
478480
pub span: Span,
479481
}
480482

compiler/rustc_trait_selection/src/traits/select/confirmation.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
11901190
ImplDerivedObligation(Box::new(ImplDerivedObligationCause {
11911191
derived,
11921192
impl_def_id,
1193+
impl_def_predicate_index: None,
11931194
span: obligation.cause.span,
11941195
}))
11951196
});

compiler/rustc_trait_selection/src/traits/select/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2608,11 +2608,12 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
26082608
assert_eq!(predicates.parent, None);
26092609
let predicates = predicates.instantiate_own(tcx, substs);
26102610
let mut obligations = Vec::with_capacity(predicates.len());
2611-
for (predicate, span) in predicates {
2611+
for (index, (predicate, span)) in predicates.into_iter().enumerate() {
26122612
let cause = cause.clone().derived_cause(parent_trait_pred, |derived| {
26132613
ImplDerivedObligation(Box::new(ImplDerivedObligationCause {
26142614
derived,
26152615
impl_def_id: def_id,
2616+
impl_def_predicate_index: Some(index),
26162617
span,
26172618
}))
26182619
});

tests/ui/derives/deriving-copyclone.stderr

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error[E0277]: the trait bound `B<C>: Copy` is not satisfied
2-
--> $DIR/deriving-copyclone.rs:31:13
2+
--> $DIR/deriving-copyclone.rs:31:26
33
|
44
LL | is_copy(B { a: 1, b: C });
5-
| ------- ^^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `B<C>`
5+
| ------- ^ the trait `Copy` is not implemented for `B<C>`
66
| |
77
| required by a bound introduced by this call
88
|
@@ -19,14 +19,14 @@ LL | fn is_copy<T: Copy>(_: T) {}
1919
= note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)
2020
help: consider borrowing here
2121
|
22-
LL | is_copy(&B { a: 1, b: C });
23-
| +
22+
LL | is_copy(B { a: 1, b: &C });
23+
| +
2424

2525
error[E0277]: the trait bound `B<C>: Clone` is not satisfied
26-
--> $DIR/deriving-copyclone.rs:32:14
26+
--> $DIR/deriving-copyclone.rs:32:27
2727
|
2828
LL | is_clone(B { a: 1, b: C });
29-
| -------- ^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `B<C>`
29+
| -------- ^ the trait `Clone` is not implemented for `B<C>`
3030
| |
3131
| required by a bound introduced by this call
3232
|
@@ -43,14 +43,14 @@ LL | fn is_clone<T: Clone>(_: T) {}
4343
= note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info)
4444
help: consider borrowing here
4545
|
46-
LL | is_clone(&B { a: 1, b: C });
47-
| +
46+
LL | is_clone(B { a: 1, b: &C });
47+
| +
4848

4949
error[E0277]: the trait bound `B<D>: Copy` is not satisfied
50-
--> $DIR/deriving-copyclone.rs:35:13
50+
--> $DIR/deriving-copyclone.rs:35:26
5151
|
5252
LL | is_copy(B { a: 1, b: D });
53-
| ------- ^^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `B<D>`
53+
| ------- ^ the trait `Copy` is not implemented for `B<D>`
5454
| |
5555
| required by a bound introduced by this call
5656
|
@@ -67,8 +67,8 @@ LL | fn is_copy<T: Copy>(_: T) {}
6767
= note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)
6868
help: consider borrowing here
6969
|
70-
LL | is_copy(&B { a: 1, b: D });
71-
| +
70+
LL | is_copy(B { a: 1, b: &D });
71+
| +
7272

7373
error: aborting due to 3 previous errors
7474

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
trait T1 {}
2+
trait T2 {}
3+
trait T3 {}
4+
trait T4 {}
5+
6+
impl<B: T2> T1 for Wrapper<B> {}
7+
8+
impl T2 for i32 {}
9+
impl T3 for i32 {}
10+
11+
impl<A: T3> T2 for Burrito<A> {}
12+
13+
struct Wrapper<W> {
14+
value: W,
15+
}
16+
17+
struct Burrito<F> {
18+
filling: F,
19+
}
20+
21+
fn want<V: T1>(_x: V) {}
22+
23+
fn example<Q>(q: Q) {
24+
want(Wrapper { value: Burrito { filling: q } });
25+
//~^ ERROR the trait bound `Q: T3` is not satisfied [E0277]
26+
}
27+
28+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
error[E0277]: the trait bound `Q: T3` is not satisfied
2+
--> $DIR/blame-trait-error.rs:24:46
3+
|
4+
LL | want(Wrapper { value: Burrito { filling: q } });
5+
| ---- ^ the trait `T3` is not implemented for `Q`
6+
| |
7+
| required by a bound introduced by this call
8+
|
9+
note: required for `Burrito<Q>` to implement `T2`
10+
--> $DIR/blame-trait-error.rs:11:13
11+
|
12+
LL | impl<A: T3> T2 for Burrito<A> {}
13+
| -- ^^ ^^^^^^^^^^
14+
| |
15+
| unsatisfied trait bound introduced here
16+
note: required for `Wrapper<Burrito<Q>>` to implement `T1`
17+
--> $DIR/blame-trait-error.rs:6:13
18+
|
19+
LL | impl<B: T2> T1 for Wrapper<B> {}
20+
| -- ^^ ^^^^^^^^^^
21+
| |
22+
| unsatisfied trait bound introduced here
23+
note: required by a bound in `want`
24+
--> $DIR/blame-trait-error.rs:21:12
25+
|
26+
LL | fn want<V: T1>(_x: V) {}
27+
| ^^ required by this bound in `want`
28+
help: consider restricting type parameter `Q`
29+
|
30+
LL | fn example<Q: T3>(q: Q) {
31+
| ++++
32+
33+
error: aborting due to previous error
34+
35+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)