about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_infer/messages.ftl4
-rw-r--r--compiler/rustc_infer/src/errors/mod.rs29
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/region.rs121
-rw-r--r--tests/ui/impl-trait/precise-capturing/forgot-to-capture-lifetime.stderr6
-rw-r--r--tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.rs30
-rw-r--r--tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.stderr67
6 files changed, 242 insertions, 15 deletions
diff --git a/compiler/rustc_infer/messages.ftl b/compiler/rustc_infer/messages.ftl
index fbe8d31370c..7a5e7159920 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 <https://github.com/rust-lang/rust/issues/100013> for more information)
diff --git a/compiler/rustc_infer/src/errors/mod.rs b/compiler/rustc_infer/src/errors/mod.rs
index a801001eaf9..ce1b0f86d03 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<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,
+    },
+}
diff --git a/compiler/rustc_infer/src/infer/error_reporting/region.rs b/compiler/rustc_infer/src/infer/error_reporting/region.rs
index 5a465f46e47..191cb23184d 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,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,
+        });
+    }
+}
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 6544837ba83..979c0ca6d7b 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 00000000000..e0b115b0ce4
--- /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<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() {}
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 00000000000..391f16d012e
--- /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<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`.