Skip to content

Commit

Permalink
Fix precise capturing suggestion for hidden type when APITs are involved
Browse files Browse the repository at this point in the history
  • Loading branch information
compiler-errors committed Jul 17, 2024
1 parent 3de0a7c commit 1d40d4c
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 1d40d4c

Please sign in to comment.