From 03bee1e1e56b10a3bff0e8eb524faacdb745cabc Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 11 Jul 2024 14:00:54 -0400 Subject: [PATCH 1/2] Suggest using precise capturing for hidden type that captures region --- .../src/infer/error_reporting/region.rs | 124 ++++++++++++++++-- .../forgot-to-capture-lifetime.stderr | 6 +- .../hidden-type-suggestion.rs | 30 +++++ .../hidden-type-suggestion.stderr | 67 ++++++++++ 4 files changed, 212 insertions(+), 15 deletions(-) create mode 100644 tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.rs create mode 100644 tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.stderr diff --git a/compiler/rustc_infer/src/infer/error_reporting/region.rs b/compiler/rustc_infer/src/infer/error_reporting/region.rs index 5a465f46e47dc..793e0c70d3cf7 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/region.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/region.rs @@ -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, }; @@ -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; @@ -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 { + 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(_) => { @@ -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; + } + 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::>() + .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, + ); + } +} diff --git a/tests/ui/impl-trait/precise-capturing/forgot-to-capture-lifetime.stderr b/tests/ui/impl-trait/precise-capturing/forgot-to-capture-lifetime.stderr index 6544837ba8329..979c0ca6d7b6e 100644 --- a/tests/ui/impl-trait/precise-capturing/forgot-to-capture-lifetime.stderr +++ b/tests/ui/impl-trait/precise-capturing/forgot-to-capture-lifetime.stderr @@ -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 } + | ++ error: aborting due to 2 previous errors diff --git a/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.rs b/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.rs new file mode 100644 index 0000000000000..e0b115b0ce412 --- /dev/null +++ b/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.rs @@ -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 { +//~^ 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 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() {} diff --git a/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.stderr b/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.stderr new file mode 100644 index 0000000000000..391f16d012e4b --- /dev/null +++ b/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.stderr @@ -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 { + | -- ------------------- 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`. From 42653c0045e46c26d2468b2aa2bba97802c08795 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 11 Jul 2024 14:14:17 -0400 Subject: [PATCH 2/2] Make it translatable too --- compiler/rustc_infer/messages.ftl | 4 +++ compiler/rustc_infer/src/errors/mod.rs | 29 +++++++++++++++++++ .../src/infer/error_reporting/region.rs | 23 +++++++-------- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_infer/messages.ftl b/compiler/rustc_infer/messages.ftl index fbe8d31370cc3..7a5e71599203e 100644 --- a/compiler/rustc_infer/messages.ftl +++ b/compiler/rustc_infer/messages.ftl @@ -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 for more information) diff --git a/compiler/rustc_infer/src/errors/mod.rs b/compiler/rustc_infer/src/errors/mod.rs index a801001eaf988..ce1b0f86d0341 100644 --- a/compiler/rustc_infer/src/errors/mod.rs +++ b/compiler/rustc_infer/src/errors/mod.rs @@ -1581,3 +1581,32 @@ pub enum ObligationCauseFailureCode { subdiags: Vec, }, } + +#[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, + }, +} diff --git a/compiler/rustc_infer/src/infer/error_reporting/region.rs b/compiler/rustc_infer/src/infer/error_reporting/region.rs index 793e0c70d3cf7..191cb23184da1 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/region.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/region.rs @@ -1288,20 +1288,18 @@ fn suggest_precise_capturing<'tcx>( _ => None, }); - let (insertion_span, pre, post) = if let Some(last_lifetime_span) = last_lifetime_span { + 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.span_suggestion_verbose( - insertion_span, - format!("add `{new_lifetime}` to the `use<...>` bound to explicitly capture it",), - format!("{pre}{new_lifetime}{post}"), - Applicability::MachineApplicable, - ); + diag.subdiagnostic(errors::AddPreciseCapturing::Existing { span, new_lifetime, pre, post }); } else { let mut captured_lifetimes = FxIndexSet::default(); let mut captured_non_lifetimes = FxIndexSet::default(); @@ -1349,11 +1347,10 @@ fn suggest_precise_capturing<'tcx>( .collect::>() .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, - ); + diag.subdiagnostic(errors::AddPreciseCapturing::New { + span: tcx.def_span(opaque_def_id).shrink_to_hi(), + new_lifetime, + concatenated_bounds, + }); } }