Skip to content
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 using precise capturing for hidden type that captures region #127619

Merged
merged 2 commits into from
Jul 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 112 additions & 12 deletions compiler/rustc_infer/src/infer/error_reporting/region.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::iter;

use rustc_data_structures::fx::FxIndexSet;
use rustc_errors::{
struct_span_code_err, Applicability, Diag, Subdiagnostic, E0309, E0310, E0311, E0495,
};
Expand All @@ -12,7 +13,7 @@ use rustc_middle::traits::ObligationCauseCode;
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::{self, IsSuggestable, Region, Ty, TyCtxt, TypeVisitableExt as _};
use rustc_span::symbol::kw;
use rustc_span::{ErrorGuaranteed, Span};
use rustc_span::{BytePos, ErrorGuaranteed, Span, Symbol};
use rustc_type_ir::Upcast as _;

use super::nice_region_error::find_anon_type;
Expand Down Expand Up @@ -1201,17 +1202,21 @@ pub fn unexpected_hidden_region_diagnostic<'a, 'tcx>(
"",
);
if let Some(reg_info) = tcx.is_suitable_region(generic_param_scope, hidden_region) {
let fn_returns = tcx.return_type_impl_or_dyn_traits(reg_info.def_id);
nice_region_error::suggest_new_region_bound(
tcx,
&mut err,
fn_returns,
hidden_region.to_string(),
None,
format!("captures `{hidden_region}`"),
None,
Some(reg_info.def_id),
)
if infcx.tcx.features().precise_capturing {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there other cases where we suggest adding a feature if using nightly? If so, that might be better!

Copy link
Member Author

@compiler-errors compiler-errors Jul 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that could be done, though I think we plan on stabilizing precise capturing soon, so not totally certain about the value. Also, having things gated on "is this a nightly compiler" adds one more weird case that's impossible to test 😅

suggest_precise_capturing(tcx, opaque_ty_key.def_id, hidden_region, &mut err);
} else {
let fn_returns = tcx.return_type_impl_or_dyn_traits(reg_info.def_id);
nice_region_error::suggest_new_region_bound(
tcx,
&mut err,
fn_returns,
hidden_region.to_string(),
None,
format!("captures `{hidden_region}`"),
None,
Some(reg_info.def_id),
)
}
}
}
ty::RePlaceholder(_) => {
Expand Down Expand Up @@ -1257,3 +1262,98 @@ pub fn unexpected_hidden_region_diagnostic<'a, 'tcx>(

err
}

fn suggest_precise_capturing<'tcx>(
tcx: TyCtxt<'tcx>,
opaque_def_id: LocalDefId,
captured_lifetime: ty::Region<'tcx>,
diag: &mut Diag<'_>,
) {
let hir::OpaqueTy { bounds, .. } =
tcx.hir_node_by_def_id(opaque_def_id).expect_item().expect_opaque_ty();

let new_lifetime = Symbol::intern(&captured_lifetime.to_string());

if let Some((args, span)) = bounds.iter().find_map(|bound| match bound {
hir::GenericBound::Use(args, span) => Some((args, span)),
_ => None,
}) {
let last_lifetime_span = args.iter().rev().find_map(|arg| match arg {
hir::PreciseCapturingArg::Lifetime(lt) => Some(lt.ident.span),
_ => None,
});

let first_param_span = args.iter().find_map(|arg| match arg {
hir::PreciseCapturingArg::Param(p) => Some(p.ident.span),
_ => None,
});

let (insertion_span, pre, post) = if let Some(last_lifetime_span) = last_lifetime_span {
(last_lifetime_span.shrink_to_hi(), ", ", "")
} else if let Some(first_param_span) = first_param_span {
(first_param_span.shrink_to_lo(), "", ", ")
} else {
(span.with_hi(span.hi() - BytePos(1)).shrink_to_hi(), "", "")
};

diag.span_suggestion_verbose(
insertion_span,
format!("add `{new_lifetime}` to the `use<...>` bound to explicitly capture it",),
format!("{pre}{new_lifetime}{post}"),
Applicability::MachineApplicable,
);
} else {
let mut captured_lifetimes = FxIndexSet::default();
let mut captured_non_lifetimes = FxIndexSet::default();

let variances = tcx.variances_of(opaque_def_id);
let mut generics = tcx.generics_of(opaque_def_id);
loop {
for param in &generics.own_params {
if variances[param.index as usize] == ty::Bivariant {
continue;
}

match param.kind {
ty::GenericParamDefKind::Lifetime => {
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;
compiler-errors marked this conversation as resolved.
Show resolved Hide resolved
}
ty::GenericParamDefKind::Type { .. }
| ty::GenericParamDefKind::Const { .. } => {
captured_non_lifetimes.insert(param.name);
}
}
}

if let Some(parent) = generics.parent {
generics = tcx.generics_of(parent);
} else {
break;
}
}

if !captured_lifetimes.insert(new_lifetime) {
// Uh, strange. This lifetime appears to already be captured...
return;
}

let concatenated_bounds = captured_lifetimes
.into_iter()
.chain(captured_non_lifetimes)
.map(|sym| sym.to_string())
.collect::<Vec<_>>()
.join(", ");

diag.span_suggestion_verbose(
tcx.def_span(opaque_def_id).shrink_to_hi(),
format!("add a `use<...>` bound to explicitly capture `{new_lifetime}`",),
format!(" + use<{concatenated_bounds}>"),
Applicability::MachineApplicable,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ LL | fn lifetime_in_hidden<'a>(x: &'a ()) -> impl Sized + use<> { x }
| | opaque type defined here
| hidden type `&'a ()` captures the lifetime `'a` as defined here
|
help: to declare that `impl Sized` captures `'a`, you can add an explicit `'a` lifetime bound
help: add `'a` to the `use<...>` bound to explicitly capture it
|
LL | fn lifetime_in_hidden<'a>(x: &'a ()) -> impl Sized + use<> + 'a { x }
| ++++
LL | fn lifetime_in_hidden<'a>(x: &'a ()) -> impl Sized + use<'a> { x }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ yay

| ++

error: aborting due to 2 previous errors

Expand Down
30 changes: 30 additions & 0 deletions tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#![feature(precise_capturing)]

fn lifetime<'a, 'b>(x: &'a ()) -> impl Sized + use<'b> {
//~^ HELP add `'a` to the `use<...>` bound
x
//~^ ERROR hidden type for
}

fn param<'a, T>(x: &'a ()) -> impl Sized + use<T> {
//~^ HELP add `'a` to the `use<...>` bound
x
//~^ ERROR hidden type for
}

fn empty<'a>(x: &'a ()) -> impl Sized + use<> {
//~^ HELP add `'a` to the `use<...>` bound
x
//~^ ERROR hidden type for
}

trait Captures<'a> {}
impl<T> Captures<'_> for T {}

fn missing<'a, 'captured, 'not_captured, Captured>(x: &'a ()) -> impl Captures<'captured> {
//~^ HELP add a `use<...>` bound
x
//~^ ERROR hidden type for
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds
--> $DIR/hidden-type-suggestion.rs:5:5
|
LL | fn lifetime<'a, 'b>(x: &'a ()) -> impl Sized + use<'b> {
| -- -------------------- opaque type defined here
| |
| hidden type `&'a ()` captures the lifetime `'a` as defined here
LL |
LL | x
| ^
|
help: add `'a` to the `use<...>` bound to explicitly capture it
|
LL | fn lifetime<'a, 'b>(x: &'a ()) -> impl Sized + use<'b, 'a> {
| ++++

error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds
--> $DIR/hidden-type-suggestion.rs:11:5
|
LL | fn param<'a, T>(x: &'a ()) -> impl Sized + use<T> {
| -- ------------------- opaque type defined here
| |
| hidden type `&'a ()` captures the lifetime `'a` as defined here
LL |
LL | x
| ^
|
help: add `'a` to the `use<...>` bound to explicitly capture it
|
LL | fn param<'a, T>(x: &'a ()) -> impl Sized + use<'a, T> {
| +++

error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds
--> $DIR/hidden-type-suggestion.rs:17:5
|
LL | fn empty<'a>(x: &'a ()) -> impl Sized + use<> {
| -- ------------------ opaque type defined here
| |
| hidden type `&'a ()` captures the lifetime `'a` as defined here
LL |
LL | x
| ^
|
help: add `'a` to the `use<...>` bound to explicitly capture it
|
LL | fn empty<'a>(x: &'a ()) -> impl Sized + use<'a> {
| ++

error[E0700]: hidden type for `impl Captures<'captured>` captures lifetime that does not appear in bounds
--> $DIR/hidden-type-suggestion.rs:26:5
|
LL | fn missing<'a, 'captured, 'not_captured, Captured>(x: &'a ()) -> impl Captures<'captured> {
| -- ------------------------ opaque type defined here
| |
| hidden type `&'a ()` captures the lifetime `'a` as defined here
LL |
LL | x
| ^
|
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

For more information about this error, try `rustc --explain E0700`.