-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
Suggest adding named lifetime when the return contains value borrowed from more than one lifetimes of function inputs #105805
Conversation
r? @estebank (rustbot has picked a reviewer for you, use r? to override) |
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 for the great fix, but the test as it is right now does not prevent regressing the issue I filed: it might still error. Yes, the output is in an stderr file, but often people run --bless
when doing refactors or similar, and don't really read what is being changed.
Ideally, the test would run rustfix, but it seems that there is no option for rustfix to apply suggestions that are maybe incorrect: https://github.com/rust-lang/rustfix/issues/200 edit: rustfix applies all suggestions, you just have to tell it to apply suggestions. see e.g. src/test/ui/cast/issue-89497.rs
ffc7685
to
8297c20
Compare
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.
r=me after addressing the unwrap and let chain nitpicks
compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs
Outdated
Show resolved
Hide resolved
error[E0700]: hidden type for `impl Iterator<Item = char>` captures lifetime that does not appear in bounds | ||
--> $DIR/issue-105227.rs:8:5 | ||
| | ||
LL | fn chars0(v :(& str, &str)) -> impl Iterator<Item = char> { | ||
| ---- hidden type `std::iter::Chain<Chars<'_>, Chars<'_>>` captures the anonymous lifetime defined here | ||
... | ||
LL | v.0.chars().chain(v.1.chars()) | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| | ||
help: to declare that `impl Iterator<Item = char>` captures `'_`, you can consider introducing a named lifetime parameter `'a` | ||
| | ||
LL | fn chars0<'a>(v :(&'a str, &'a str)) -> impl Iterator<Item = char> + 'a { | ||
| ++++ ++ ++ ++++ |
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.
It'd be amazing if we could deduplicate these E0700s based on the primary span, precisely so that we can turn this test into a // run-rustfix
test (and reduce verbosity to boot. We might even go further and not just hide the later one but rather incorporate all the spans pointing at the params capturing the hidden lifetimes.
This should be follow up work.
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 already have a buffering mechanism, but it occurs at the Diagnostic
level. I wonder if we should move these to operate on a higher level enum or struct so that we have more info available to merge multiple errors into one.
rust/compiler/rustc_borrowck/src/diagnostics/region_errors.rs
Lines 215 to 226 in a5cd3bd
RegionErrorKind::UnexpectedHiddenRegion { span, hidden_ty, key, member_region } => { | |
let named_ty = self.regioncx.name_regions(self.infcx.tcx, hidden_ty); | |
let named_key = self.regioncx.name_regions(self.infcx.tcx, key); | |
let named_region = self.regioncx.name_regions(self.infcx.tcx, member_region); | |
self.buffer_error(unexpected_hidden_region_diagnostic( | |
self.infcx.tcx, | |
span, | |
named_ty, | |
named_region, | |
named_key, | |
)); | |
} |
Seeing this I wonder if the deduplication logic is not triggering because of the secondary spans only.
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 deduplication logic in fn emit_diagnostic(&mut self, diagnostic: &mut Diagnostic)
will not be triggered because the two spans in span_lables
field of two diagnostic
records are different:
Diagnostic {.., span: MultiSpan { primary_spans: [issue-105227.rs:8:5: 8:35 (#0)], span_labels: [(issue-105227.rs:5:15: 5:20 (#0), Str("hidden type `std::iter::Chain<Chars<'_>, Chars<'_>>` captures the anonymous lifetime defined here"))] }..}
vs
Diagnostic {.., span: MultiSpan { primary_spans: [issue-105227.rs:8:5: 8:35 (#0)], span_labels: [(issue-105227.rs:5:22: 5:26 (#0), Str("hidden type `std::iter::Chain<Chars<'_>, Chars<'_>>` captures the anonymous lifetime defined here"))] }..}
Although the primary_spans
and label
field of span_labels
are the same.
We may take out the span in span_labels
when doing hash for diagnostic
records to mark them duplicates for the case, but need to take care of any side effects.
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've tried the following code change to deduplicate these E0700s based on the (primary_span, named_ty and named_key) tuple, seems working as expected without side effects:
pub(crate) fn report_region_errors(&mut self, nll_errors: RegionErrors<'tcx>) {
// Iterate through all the errors, producing a diagnostic for each one. The diagnostics are
// buffered in the `MirBorrowckCtxt`.
let mut outlives_suggestion = OutlivesSuggestionBuilder::default();
+ let mut last_unexpected_hidden_region: Option<(Span, Ty<'_>, ty::OpaqueTypeKey<'tcx>)> = None;
for nll_error in nll_errors.into_iter() {
match nll_error {
... ...
RegionErrorKind::UnexpectedHiddenRegion { span, hidden_ty, key, member_region } => {
let named_ty = self.regioncx.name_regions(self.infcx.tcx, hidden_ty);
let named_key = self.regioncx.name_regions(self.infcx.tcx, key);
let named_region = self.regioncx.name_regions(self.infcx.tcx, member_region);
+ if last_unexpected_hidden_region != Some((span, named_ty, named_key)) {
self.buffer_error(unexpected_hidden_region_diagnostic(
self.infcx.tcx,
span,
named_ty,
named_region,
named_key,
));
+ last_unexpected_hidden_region = Some((span, named_ty, named_key));
+ }
All tests passed and including this test with run-rustfix
enabled. Is this a reasonable solution for fixing the issue?
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 only thing I'd change is in the else
branch to still construct the error but call .delay_as_bug()
on it. That way, if there's some fluke caused by a future refactor where no errors are emitted we still won't emit an invalid binary.
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 for the advice. Added .delay_as_bug()
in the else
branch.
8297c20
to
20dae2f
Compare
spans_suggs | ||
.push((generics.span.shrink_to_hi(), "<'a>".to_string())); |
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 suspect that with the new requested test this logic will no longer be good enough. Have you looked at the existing suggestion machinery? You might be able to call a function to get this information, instead of having to replicate it here.
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 closest function I could found for suggesting adding named lifetime is fn add_missing_lifetime_specifiers_label
in compiler/rustc_resolve/src/late/diagnostics.rs
, but it cannot be used directly for this purpose. The logic of adding a new lifetime properly without conflicting with existing lifetime in this function could be borrowed here to solve the issue. It seems to require quite a bit of code and probably needs to create a separate function for this, unless such a function already exists which I'm not aware of.
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.
Why can't it be used? Because of the crate it is in? We have freedom to move logic from each crate to an earlier one, if it would still work (an example of something that wouldn't work is if the logic operates on the HIR but all we have available is the AST).
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.
You can use generics.span_for_lifetime_suggestion()
.
This PR replaces all anonymous lifetimes by a named lifetime. Can we pass the ty::Region
corresponding to the hidden region? This could allow to only replace a single lifetime.
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 for the advice. I used the logic in generics.span_for_lifetime_suggestion()
to identify if the fn contains a lifetime parameter and what the parameter name is. If there are more than one lifetime parameters, I will use the first one.
spans_suggs | ||
.push((generics.span.shrink_to_hi(), "<'a>".to_string())); |
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.
You can use generics.span_for_lifetime_suggestion()
.
This PR replaces all anonymous lifetimes by a named lifetime. Can we pass the ty::Region
corresponding to the hidden region? This could allow to only replace a single lifetime.
.params | ||
.iter() | ||
.filter(|p| p.is_elided_lifetime()) | ||
.map(|p| (p.span.shrink_to_hi(), "'a ".to_string())) |
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.
This is incorrect in general. If p
is '_
, this will suggest '_'a
. If p
is in an elided path like T
or T<()>
, we'd get weirder suggestions. For hir::Lifetime
, there is suggestion_position
which allows to chose which case. The same logic applies to GenericParam
.
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.
Resolved the '_'a
issue by conditionally use span of span.shrink_to_hi()
based on the span area size. Since it is elided lifetime in the function inputs, there seems only two possibilities: with or without '_
, which can be identified by the span area size. Are there any possibilities to having T
or T<()>
cases in function input parameters? If there is, could you provide me a test case for it? Thanks!
20dae2f
to
ec4390c
Compare
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 a few nitpicks, but I'm r=me on this after addressing those.
compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs
Outdated
Show resolved
Hide resolved
compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs
Outdated
Show resolved
Hide resolved
…d from more than one lifetimes of the function's inputs
ec4390c
to
523fe7a
Compare
@bors r+ |
☀️ Test successful - checks-actions |
Finished benchmarking commit (7bbbaab): comparison URL. Overall result: no relevant changes - no action needed@rustbot label: -perf-regression Instruction countThis benchmark run did not return any relevant results for this metric. Max RSS (memory usage)ResultsThis is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
CyclesResultsThis is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
|
fix for #105227.
The problem: The suggestion of adding an explicit
'_
lifetime bound is incorrect when the function's return type contains a value which could be borrowed from more than one lifetimes of the function's inputs. Instead, a named lifetime parameter can be introduced in such a case.The solution: Checking the number of elided lifetimes in the function signature. If more than one lifetimes found in the function inputs when the suggestion of adding explicit
'_
lifetime, change it to using named lifetime parameter'a
instead.