-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refine error spans for "The trait bound T: Trait
is not satisfied" when passing literal structs/tuples
#106477
Refine error spans for "The trait bound T: Trait
is not satisfied" when passing literal structs/tuples
#106477
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @compiler-errors (or someone else) soon. Please see the contribution instructions for more information. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left mostly a bunch of style nits. I've gotta take a few more passes over this code to reason about its correctness :)
}; | ||
|
||
// update the error span. | ||
error.obligation.cause.span = expr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, so the span diverging from the actual hir id of the new obligation cause is very suspicious. It'll probably cause incorrect suggestions, since those can look at the type of the argument in the obligation cause via TypeckResults.
hir::ExprKind::Struct( | ||
hir::QPath::Resolved( | ||
_, | ||
hir::Path { res: hir::def::Res::Def(struct_def_kind, struct_def_id), .. }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be a way of extracting the def here rather than destructuring the path's res -- try type_dependent_def
This will help us in cases where the path is resolved during type-check rather than during the earlier name resolve stage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used .qpath_res
here instead; type_dependent_def
doesn't seem to work, I think because this isn't a call?
There's a second use of pretty much the same logic below, where type_dependent_def
might work. I haven't tried that yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad -- qpath_res
is the one I was thinking of, yeah. That'll store the properly resolved res for a qpath, regardless of it's TypeRelative
or Resolved
let struct_def_generics: Vec<&ty::GenericParamDef> = relevant_ty_args_indices | ||
.into_iter() | ||
.filter_map(|index| struct_def_generics.params.get(index)) | ||
.collect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this, and similar below, could be a retain
:laugh:
This comment has been minimized.
This comment has been minimized.
I'm not sure if I understand this comment, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some more mostly nit-picks.
Needless to say, I'm quite happy with the state of this PR as-is, so please don't take my comments in a negative light 😃
src/test/ui/traits/negative-impls/negated-auto-traits-error.stderr
Outdated
Show resolved
Hide resolved
I've fixed up most of the small nits, and marked those comments with (👍) - I'm not sure for rustc if the policy is to prefer the reviewer resolve their own comments, or the PR author? I'll try to get to the rest of the nits tomorrow. They all seem reasonable, it will just take me a bit to fix some of them. |
Feel free to click resolve as you fix things. I'll resolve the ones I see are outdated when I do another review of this tomorrow. And sure, take your time addressing feedback, and as always, speak up if you disagree with my suggestions. |
I meant that |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
return true; | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The old way of doing things and the new way of doing things introduced in this PR are in conflict. The old way mutate the span in place, and returns true
if it does this.
The new way uses Result<Expr, Expr>
to indicate whether it successfully refined or unsuccessfully refined. But it can still make a partial refinement in the unsuccessful case. The downside is that it needs to know the "root" expr
to return it in failure.
I think a hybrid approach is actually going to be nicer. I will introduce a new ErrorSpanRefiner
which can be called with:
struct FullyRefined;
struct NotFullyRefined;
fn refine_span_to_expr(&mut self, expr: &hir::Expr<'tcx>) -> Result<(), NotFullyRefined>
and the new/existing point_to_*_
functions will be updated to return Result<FullyRefined, NotFullyRefined>
instead of bool
or Result<&Expr, &Expr>
.
The ErrorSpanRefiner
can also deal with updating the cause, as needed, so that the cause and the span stay in sync.
This comment has been minimized.
This comment has been minimized.
@@ -1863,23 +1863,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { | |||
return true; | |||
} | |||
} | |||
hir::ExprKind::Struct(qpath, fields, ..) => { | |||
if let Res::Def(DefKind::Struct | DefKind::Variant, variant_def_id) = | |||
hir::ExprKind::Struct(qpath, _fields, ..) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another thing you can do after the rest of the review is done -- all of these methods under adjust_fulfillment_errors_for_expr_obligation
can be moved into a distinct module. Maybe adjust_fulfillment_errors.rs
or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran into some merge conflicts trying to move the existing functions - the new ones are currently placed into a separate module. I'd like to move the remaining ones in a separate PR, just to avoid rebase-churn on this one, since it adds a lot of net-new code.
a2b69cf
to
56e2593
Compare
This comment has been minimized.
This comment has been minimized.
☔ The latest upstream changes (presumably #106637) made this pull request unmergeable. Please resolve the merge conflicts. |
Howdy -- sorry for not reviewing this in the mean time. This PR needs to be rebased and tests need to be fixed/blessed. Can you do that @Nathan-Fenner? (no rush obviously) |
(use @rustbot author |
56e2593
to
aa30dd7
Compare
Some changes occurred in src/tools/cargo cc @ehuss |
aa30dd7
to
bd8812b
Compare
This comment has been minimized.
This comment has been minimized.
bd8812b
to
2a67e99
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Subtle reminder for the future to use @rustbot ready to mark this as ready for review. Sorry, hadn't seen that this was updated because it was marked as |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I'm pretty happy with the changes to the logic.
Err(expr) | ||
} | ||
|
||
// FIXME: This can be made into a private, non-impl function later. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should follow-up with these FIXMEs. If you can't in the next few weeks, then I will.
@bors r+ |
…iaskrgr Rollup of 9 pull requests Successful merges: - rust-lang#106477 (Refine error spans for "The trait bound `T: Trait` is not satisfied" when passing literal structs/tuples) - rust-lang#107596 (Add nicer output to PGO build timer) - rust-lang#107692 (Sort Generator `print-type-sizes` according to their yield points) - rust-lang#107714 (Clarify wording on f64::round() and f32::round()) - rust-lang#107720 (end entry paragraph with a period (.)) - rust-lang#107724 (remove unused rustc_* imports) - rust-lang#107725 (Turn MarkdownWithToc into a struct with named fields) - rust-lang#107731 (interpret: move discriminant reading and writing to separate file) - rust-lang#107735 (Add mailmap for commits made by xes@meta.com) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
…ust_fulfillment_errors, r=compiler-errors Split fn_ctxt/adjust_fulfillment_errors from fn_ctxt/checks This is a follow-up from rust-lang#106477, addressing a small number of the `FIXME`s that were added, by moving some functions into the new(er) `adjust_fulfillment_errors` module. More cleanup is possible for this file (and I'll hopefully get around to doing some of that soon) but the very first thing is to just move these functions out. There should be no "real" changes in this PR, besides minor adjustments to imports and the functions being transferred.
…ef-trait-refine, r=WaffleLapkin Refine error span for trait error into borrowed expression Extends the error span refinement in rust-lang#106477 to drill into borrowed expressions just like tuples/struct/enum literals. For example, ```rs trait Fancy {} trait Good {} impl <'a, T> Fancy for &'a T where T: Good {} impl <S> Good for Option<S> where S: Iterator {} fn want_fancy<F>(f: F) where F: Fancy {} fn example() { want_fancy(&Some(5)); // (BEFORE) ^^^^^^^^ `{integer}` is not an iterator // (AFTER) ^ `{integer}` is not an iterator } ``` Existing heuristics try to find the right part of the expression to "point at"; current heuristics look at e.g. struct constructors and tuples. This PR adds a new check for borrowed expressions when looking into a borrowed type.
…-span-fix-Some, r=WaffleLapkin Point error span at Some constructor argument when trait resolution fails This is a follow up to rust-lang#108254 and rust-lang#106477 which extends error span refinement to handle a case which I mistakenly believed was handled in rust-lang#106477. The goal is to refine the error span depicted below: ```rs trait Fancy {} impl <T> Fancy for Option<T> where T: Iterator {} fn want_fancy<F>(f: F) where F: Fancy {} fn example() { want_fancy(Some(5)); // (BEFORE) ^^^^^^^ `{integer}` is not an iterator // (AFTER) ^ `{integer}` is not an iterator } ``` I had used a (slightly more complex) example as an illustrative example in rust-lang#108254 , but hadn't actually turned it into a test, because I had (incorrectly) believed at the time it was covered by existing behavior. It turns out that `Some` is slightly "special" in that it resolves differently from the other `enum` constructors I had tried, and therefore this test was actually broken. I've now updated the tests to include this example, and fixed the code to correctly resolve the `Some` constructor so that the span of the error is reduced.
…-span-fix-Some, r=WaffleLapkin Point error span at Some constructor argument when trait resolution fails This is a follow up to rust-lang#108254 and rust-lang#106477 which extends error span refinement to handle a case which I mistakenly believed was handled in rust-lang#106477. The goal is to refine the error span depicted below: ```rs trait Fancy {} impl <T> Fancy for Option<T> where T: Iterator {} fn want_fancy<F>(f: F) where F: Fancy {} fn example() { want_fancy(Some(5)); // (BEFORE) ^^^^^^^ `{integer}` is not an iterator // (AFTER) ^ `{integer}` is not an iterator } ``` I had used a (slightly more complex) example as an illustrative example in rust-lang#108254 , but hadn't actually turned it into a test, because I had (incorrectly) believed at the time it was covered by existing behavior. It turns out that `Some` is slightly "special" in that it resolves differently from the other `enum` constructors I had tried, and therefore this test was actually broken. I've now updated the tests to include this example, and fixed the code to correctly resolve the `Some` constructor so that the span of the error is reduced.
…-span-fix-Some, r=WaffleLapkin Point error span at Some constructor argument when trait resolution fails This is a follow up to rust-lang#108254 and rust-lang#106477 which extends error span refinement to handle a case which I mistakenly believed was handled in rust-lang#106477. The goal is to refine the error span depicted below: ```rs trait Fancy {} impl <T> Fancy for Option<T> where T: Iterator {} fn want_fancy<F>(f: F) where F: Fancy {} fn example() { want_fancy(Some(5)); // (BEFORE) ^^^^^^^ `{integer}` is not an iterator // (AFTER) ^ `{integer}` is not an iterator } ``` I had used a (slightly more complex) example as an illustrative example in rust-lang#108254 , but hadn't actually turned it into a test, because I had (incorrectly) believed at the time it was covered by existing behavior. It turns out that `Some` is slightly "special" in that it resolves differently from the other `enum` constructors I had tried, and therefore this test was actually broken. I've now updated the tests to include this example, and fixed the code to correctly resolve the `Some` constructor so that the span of the error is reduced.
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:
(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 onlyKale
is the "problematic" part, we can point at it specifically. In particular, the primary error message itself mentions the missingKale: 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:Food: Delicious
and find which generics it mentions (in this case, onlyFood
)Filling
(in this case, onlyfood
)This PR extends this heuristic by further refining the resulting expression span in the new
point_at_specific_expr_if_possible
function. For eachimpl
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:
impl
(impl <Filling: Delicious> Delicious for Burrito<Filling>
)that links our obligation (
Kale: Delicious
) with the parent obligation (Burrito<Kale>: Delicious
)Filling: Delicious
) which produced our obligation.Filling
).Self
type in theimpl
, and see which of its type argument(s) mention any of those generics.Self
type, and identify (for each of its variants) if there's a unique fieldwhich uses those generic arguments.
Before we do any of this logic, we recursively call
point_at_specific_expr_if_possible
on the parentobligation. 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 theExpr
whose span should bereported as an error. If it is
Ok
, then it means it refined successfull. If it isErr
, then it may beonly 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
vsenum
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:
Especially with large tuples, the existing error spans are not very effective at quickly narrowing down the source of the problem.