about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2024-07-12 16:18:51 -0400
committerMichael Goulet <michael@errs.io>2024-07-17 10:52:13 -0400
commit1d40d4c4f496ce26d2cbf268b2f415e1f3bd5616 (patch)
tree3718158d1e2df3eb6c360ea8a1701cb083ade3d7
parent3de0a7c716daab58bdb6551b0d0af6a466486639 (diff)
downloadrust-1d40d4c4f496ce26d2cbf268b2f415e1f3bd5616.tar.gz
rust-1d40d4c4f496ce26d2cbf268b2f415e1f3bd5616.zip
Fix precise capturing suggestion for hidden type when APITs are involved
-rw-r--r--compiler/rustc_infer/messages.ftl5
-rw-r--r--compiler/rustc_infer/src/error_reporting/infer/region.rs104
-rw-r--r--compiler/rustc_infer/src/errors/mod.rs22
-rw-r--r--tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.rs12
-rw-r--r--tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.stderr44
5 files changed, 170 insertions, 17 deletions
diff --git a/compiler/rustc_infer/messages.ftl b/compiler/rustc_infer/messages.ftl
index 7a5e7159920..c279195a7e9 100644
--- a/compiler/rustc_infer/messages.ftl
+++ b/compiler/rustc_infer/messages.ftl
@@ -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)
@@ -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
diff --git a/compiler/rustc_infer/src/error_reporting/infer/region.rs b/compiler/rustc_infer/src/error_reporting/infer/region.rs
index 093d2d3d743..5d41bb5d271 100644
--- a/compiler/rustc_infer/src/error_reporting/infer/region.rs
+++ b/compiler/rustc_infer/src/error_reporting/infer/region.rs
@@ -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 {
@@ -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 {
@@ -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 { .. } => {
@@ -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,
+            });
+        }
     }
 }
diff --git a/compiler/rustc_infer/src/errors/mod.rs b/compiler/rustc_infer/src/errors/mod.rs
index f849a1a7322..2ce712e0bff 100644
--- a/compiler/rustc_infer/src/errors/mod.rs
+++ b/compiler/rustc_infer/src/errors/mod.rs
@@ -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);
+    }
+}
diff --git a/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.rs b/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.rs
index e0b115b0ce4..b50780643f1 100644
--- a/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.rs
+++ b/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.rs
@@ -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() {}
diff --git a/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.stderr b/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.stderr
index 391f16d012e..1007a835894 100644
--- a/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.stderr
+++ b/tests/ui/impl-trait/precise-capturing/hidden-type-suggestion.stderr
@@ -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`.