Skip to content

Commit

Permalink
Rollup merge of rust-lang#127664 - compiler-errors:precise-capturing-…
Browse files Browse the repository at this point in the history
…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 rust-lang#127619, only the last commit is relevant.
r? oli-obk

Tracking:

- rust-lang#123432
  • Loading branch information
tgross35 committed Jul 18, 2024
2 parents 973d92c + 1d40d4c commit b5771e7
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 17 deletions.
5 changes: 5 additions & 0 deletions compiler/rustc_infer/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
104 changes: 88 additions & 16 deletions compiler/rustc_infer/src/error_reporting/infer/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 { .. } => {
Expand All @@ -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(", ");

diag.subdiagnostic(errors::AddPreciseCapturing::New {
span: tcx.def_span(opaque_def_id).shrink_to_hi(),
new_lifetime,
concatenated_bounds,
});
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,
});
} 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,
});
}
}
}
22 changes: 22 additions & 0 deletions compiler/rustc_infer/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
12 changes: 12 additions & 0 deletions tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

0 comments on commit b5771e7

Please sign in to comment.