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 all commits
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
4 changes: 4 additions & 0 deletions compiler/rustc_infer/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ infer_opaque_hidden_type =

infer_outlives_bound = lifetime of the source pointer does not outlive lifetime bound of the object type
infer_outlives_content = lifetime of reference outlives lifetime of borrowed content...

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_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
29 changes: 29 additions & 0 deletions compiler/rustc_infer/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1581,3 +1581,32 @@ pub enum ObligationCauseFailureCode {
subdiags: Vec<TypeErrorAdditionalDiags>,
},
}

#[derive(Subdiagnostic)]
pub enum AddPreciseCapturing {
#[suggestion(
infer_precise_capturing_new,
style = "verbose",
code = " + use<{concatenated_bounds}>",
applicability = "machine-applicable"
)]
New {
#[primary_span]
span: Span,
new_lifetime: Symbol,
concatenated_bounds: String,
},
#[suggestion(
infer_precise_capturing_existing,
style = "verbose",
code = "{pre}{new_lifetime}{post}",
applicability = "machine-applicable"
)]
Existing {
#[primary_span]
span: Span,
new_lifetime: Symbol,
pre: &'static str,
post: &'static str,
},
}
121 changes: 109 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,95 @@ 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 (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 {
// If we have no args, then have `use<>` and need to fall back to using
// span math. This sucks, but should be reliable due to the construction
// of the `use<>` span.
(span.with_hi(span.hi() - BytePos(1)).shrink_to_hi(), "", "")
};

diag.subdiagnostic(errors::AddPreciseCapturing::Existing { span, new_lifetime, pre, post });
} 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;
}
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.subdiagnostic(errors::AddPreciseCapturing::New {
span: tcx.def_span(opaque_def_id).shrink_to_hi(),
new_lifetime,
concatenated_bounds,
});
}
}
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`.
Loading