Rollup merge of #127664 - compiler-errors:precise-capturing-better-sugg-apit, r=oli-obk

Fix precise capturing suggestion for hidden regions when we have APITs

Suggests to turn APITs into type parameters so they can be named in precise capturing syntax for hidden type lifetime errors. We also note that it may change the API.

This is currently done via a note *and* a suggestion, which feels a bit redundant, but I wasn't totally sure of a better alternative for the presentation.

Code is kind of a mess but there's a lot of cases to consider. Happy to iterate on this if you think the approach is too messy.

Based on #127619, only the last commit is relevant.
r? oli-obk

Tracking:

- https://github.com/rust-lang/rust/issues/123432
This commit is contained in:
Trevor Gross 2024-07-17 19:53:26 -05:00 committed by GitHub
commit b5771e7e0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 169 additions and 16 deletions

View File

@ -225,6 +225,8 @@ infer_outlives_content = lifetime of reference outlives lifetime of borrowed con
infer_precise_capturing_existing = add `{$new_lifetime}` to the `use<...>` bound to explicitly capture it
infer_precise_capturing_new = add a `use<...>` bound to explicitly capture `{$new_lifetime}`
infer_precise_capturing_new_but_apit = add a `use<...>` bound to explicitly capture `{$new_lifetime}` after turning all argument-position `impl Trait` into type parameters, noting that this possibly affects the API of this crate
infer_prlf_defined_with_sub = the lifetime `{$sub_symbol}` defined here...
infer_prlf_defined_without_sub = the lifetime defined here...
infer_prlf_known_limitation = this is a known limitation that will be removed in the future (see issue #100013 <https://github.com/rust-lang/rust/issues/100013> for more information)
@ -387,6 +389,9 @@ infer_type_annotations_needed = {$source_kind ->
.label = type must be known at this point
infer_types_declared_different = these two types are declared with different lifetimes...
infer_warn_removing_apit_params = you could use a `use<...>` bound to explicitly capture `{$new_lifetime}`, but argument-position `impl Trait`s are not nameable
infer_where_copy_predicates = copy the `where` clause predicates from the trait
infer_where_remove = remove the `where` clause

View File

@ -1269,9 +1269,13 @@ fn suggest_precise_capturing<'tcx>(
captured_lifetime: ty::Region<'tcx>,
diag: &mut Diag<'_>,
) {
let hir::OpaqueTy { bounds, .. } =
let hir::OpaqueTy { bounds, origin, .. } =
tcx.hir_node_by_def_id(opaque_def_id).expect_item().expect_opaque_ty();
let hir::OpaqueTyOrigin::FnReturn(fn_def_id) = *origin else {
return;
};
let new_lifetime = Symbol::intern(&captured_lifetime.to_string());
if let Some((args, span)) = bounds.iter().find_map(|bound| match bound {
@ -1306,6 +1310,7 @@ fn suggest_precise_capturing<'tcx>(
let variances = tcx.variances_of(opaque_def_id);
let mut generics = tcx.generics_of(opaque_def_id);
let mut synthetics = vec![];
loop {
for param in &generics.own_params {
if variances[param.index as usize] == ty::Bivariant {
@ -1317,9 +1322,7 @@ fn suggest_precise_capturing<'tcx>(
captured_lifetimes.insert(param.name);
}
ty::GenericParamDefKind::Type { synthetic: true, .. } => {
// FIXME: We can't provide a good suggestion for
// `use<...>` if we have an APIT. Bail for now.
return;
synthetics.push((tcx.def_span(param.def_id), param.name));
}
ty::GenericParamDefKind::Type { .. }
| ty::GenericParamDefKind::Const { .. } => {
@ -1340,17 +1343,86 @@ fn suggest_precise_capturing<'tcx>(
return;
}
let concatenated_bounds = captured_lifetimes
.into_iter()
.chain(captured_non_lifetimes)
.map(|sym| sym.to_string())
.collect::<Vec<_>>()
.join(", ");
if synthetics.is_empty() {
let concatenated_bounds = captured_lifetimes
.into_iter()
.chain(captured_non_lifetimes)
.map(|sym| sym.to_string())
.collect::<Vec<_>>()
.join(", ");
diag.subdiagnostic(errors::AddPreciseCapturing::New {
span: tcx.def_span(opaque_def_id).shrink_to_hi(),
new_lifetime,
concatenated_bounds,
});
diag.subdiagnostic(errors::AddPreciseCapturing::New {
span: tcx.def_span(opaque_def_id).shrink_to_hi(),
new_lifetime,
concatenated_bounds,
});
} else {
let mut next_fresh_param = || {
["T", "U", "V", "W", "X", "Y", "A", "B", "C"]
.into_iter()
.map(Symbol::intern)
.chain((0..).map(|i| Symbol::intern(&format!("T{i}"))))
.find(|s| captured_non_lifetimes.insert(*s))
.unwrap()
};
let mut new_params = String::new();
let mut suggs = vec![];
let mut apit_spans = vec![];
for (i, (span, name)) in synthetics.into_iter().enumerate() {
apit_spans.push(span);
let fresh_param = next_fresh_param();
// Suggest renaming.
suggs.push((span, fresh_param.to_string()));
// Super jank. Turn `impl Trait` into `T: Trait`.
//
// This currently involves stripping the `impl` from the name of
// the parameter, since APITs are always named after how they are
// rendered in the AST. This sucks! But to recreate the bound list
// from the APIT itself would be miserable, so we're stuck with
// this for now!
if i > 0 {
new_params += ", ";
}
let name_as_bounds = name.as_str().trim_start_matches("impl").trim_start();
new_params += fresh_param.as_str();
new_params += ": ";
new_params += name_as_bounds;
}
let Some(generics) = tcx.hir().get_generics(fn_def_id) else {
// This shouldn't happen, but don't ICE.
return;
};
// Add generics or concatenate to the end of the list.
suggs.push(if let Some(params_span) = generics.span_for_param_suggestion() {
(params_span, format!(", {new_params}"))
} else {
(generics.span, format!("<{new_params}>"))
});
let concatenated_bounds = captured_lifetimes
.into_iter()
.chain(captured_non_lifetimes)
.map(|sym| sym.to_string())
.collect::<Vec<_>>()
.join(", ");
suggs.push((
tcx.def_span(opaque_def_id).shrink_to_hi(),
format!(" + use<{concatenated_bounds}>"),
));
diag.subdiagnostic(errors::AddPreciseCapturingAndParams {
suggs,
new_lifetime,
apit_spans,
});
}
}
}

View File

@ -1609,3 +1609,25 @@ pub enum AddPreciseCapturing {
post: &'static str,
},
}
pub struct AddPreciseCapturingAndParams {
pub suggs: Vec<(Span, String)>,
pub new_lifetime: Symbol,
pub apit_spans: Vec<Span>,
}
impl Subdiagnostic for AddPreciseCapturingAndParams {
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
self,
diag: &mut Diag<'_, G>,
_f: &F,
) {
diag.arg("new_lifetime", self.new_lifetime);
diag.multipart_suggestion_verbose(
fluent::infer_precise_capturing_new_but_apit,
self.suggs,
Applicability::MaybeIncorrect,
);
diag.span_note(self.apit_spans, fluent::infer_warn_removing_apit_params);
}
}

View File

@ -27,4 +27,16 @@ fn missing<'a, 'captured, 'not_captured, Captured>(x: &'a ()) -> impl Captures<'
//~^ ERROR hidden type for
}
fn no_params_yet(_: impl Sized, y: &()) -> impl Sized {
//~^ HELP add a `use<...>` bound
y
//~^ ERROR hidden type for
}
fn yes_params_yet<'a, T>(_: impl Sized, y: &'a ()) -> impl Sized {
//~^ HELP add a `use<...>` bound
y
//~^ ERROR hidden type for
}
fn main() {}

View File

@ -62,6 +62,48 @@ help: add a `use<...>` bound to explicitly capture `'a`
LL | fn missing<'a, 'captured, 'not_captured, Captured>(x: &'a ()) -> impl Captures<'captured> + use<'captured, 'a, Captured> {
| ++++++++++++++++++++++++++++++
error: aborting due to 4 previous errors
error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds
--> $DIR/hidden-type-suggestion.rs:32:5
|
LL | fn no_params_yet(_: impl Sized, y: &()) -> impl Sized {
| --- ---------- opaque type defined here
| |
| hidden type `&()` captures the anonymous lifetime defined here
LL |
LL | y
| ^
|
note: you could use a `use<...>` bound to explicitly capture `'_`, but argument-position `impl Trait`s are not nameable
--> $DIR/hidden-type-suggestion.rs:30:21
|
LL | fn no_params_yet(_: impl Sized, y: &()) -> impl Sized {
| ^^^^^^^^^^
help: add a `use<...>` bound to explicitly capture `'_` after turning all argument-position `impl Trait` into type parameters, noting that this possibly affects the API of this crate
|
LL | fn no_params_yet<T: Sized>(_: T, y: &()) -> impl Sized + use<'_, T> {
| ++++++++++ ~ ++++++++++++
error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds
--> $DIR/hidden-type-suggestion.rs:38:5
|
LL | fn yes_params_yet<'a, T>(_: impl Sized, y: &'a ()) -> impl Sized {
| -- ---------- opaque type defined here
| |
| hidden type `&'a ()` captures the lifetime `'a` as defined here
LL |
LL | y
| ^
|
note: you could use a `use<...>` bound to explicitly capture `'a`, but argument-position `impl Trait`s are not nameable
--> $DIR/hidden-type-suggestion.rs:36:29
|
LL | fn yes_params_yet<'a, T>(_: impl Sized, y: &'a ()) -> impl Sized {
| ^^^^^^^^^^
help: add a `use<...>` bound to explicitly capture `'a` after turning all argument-position `impl Trait` into type parameters, noting that this possibly affects the API of this crate
|
LL | fn yes_params_yet<'a, T, U: Sized>(_: U, y: &'a ()) -> impl Sized + use<'a, T, U> {
| ++++++++++ ~ +++++++++++++++
error: aborting due to 6 previous errors
For more information about this error, try `rustc --explain E0700`.