rust-lang/rust · #165194

name elided lifetimes in `use<...>` suggestion when `impl Trait` params are present

Albab-Hasan · merged Nov 7, 20267 files · 329 + / 73 −
compiler/rustc_trait_selection/src/diagnostics.rs19 + / 0 −
@@ -1921,6 +1921,25 @@ impl Subdiagnostic for AddPreciseCapturingAndParams {     } } +pub(crate) struct AddPreciseCapturingNameLifetimesAndParams {+    pub suggs: Vec<(Span, String)>,+    pub apit_spans: Vec<Span>,+}++impl Subdiagnostic for AddPreciseCapturingNameLifetimesAndParams {+    fn add_to_diag(self, diag: &mut Diag<'_>) {+        diag.multipart_suggestion(+            msg!("add a `use<...>` bound and name the elided lifetimes after turning all argument-position `impl Trait` into type parameters, noting that this possibly affects the API of this crate"),+            self.suggs,+            Applicability::MaybeIncorrect,+        );+        diag.span_note(+            self.apit_spans,+            msg!("argument-position `impl Trait` cannot be named in a `use<...>` bound and must be turned into type parameters"),+        );+    }+}+ pub(crate) struct AddPreciseCapturingAndNameLifetimes {     pub suggs: Vec<(Span, String)>, }
compiler/rustc_trait_selection/src/error_reporting/infer/region.rs101 + / 73 −
@@ -1528,26 +1528,70 @@ fn suggest_precise_capturing<'tcx>(             return;         } +        let mut next_fresh_param = || {+            ['T', 'U', 'V', 'W', 'X', 'Y', 'A', 'B', 'C']+                .into_iter()+                .map(sym::character)+                .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;+        }+         // `'_` in a capture list only resolves when the signature has a single anonymous         // lifetime, so when there are several the suggestion has to name them first.-        if synthetics.is_empty()-            && let Some(suggs) = name_anon_lifetimes_suggestion(-                tcx,-                generic_param_scope,-                opaque_def_id,-                fn_def_id,-                hidden_ty,-                &captured_lifetimes,-                &captured_non_lifetimes,-            )-        {-            diag.subdiagnostic(diagnostics::AddPreciseCapturingAndNameLifetimes { suggs });-            return;-        }+        let named_suggs = name_anon_lifetimes_suggestion(+            tcx,+            generic_param_scope,+            opaque_def_id,+            fn_def_id,+            hidden_ty,+            &captured_lifetimes,+            &captured_non_lifetimes,+        )+        // a lifetime inside an APIT bound gets its own replacement part from+        // `LifetimeReplaceVisitor`, whose span falls inside the APIT span. overlapping parts in one+        // multipart suggestion panic the emitter, so the filter drops the whole named suggestion+        // and falls back to `'_`.+        .filter(|named| {+            !named.iter().any(|(span, _)| apit_spans.iter().any(|apit| apit.overlaps(*span)))+        }); -        captured_lifetimes.insert(new_lifetime);+        if apit_spans.is_empty() {+            if let Some(suggs) = named_suggs {+                diag.subdiagnostic(diagnostics::AddPreciseCapturingAndNameLifetimes { suggs });+                return;+            }++            captured_lifetimes.insert(new_lifetime); -        if synthetics.is_empty() {             let concatenated_bounds = captured_lifetimes                 .into_iter()                 .chain(captured_non_lifetimes)@@ -1561,72 +1605,56 @@ fn suggest_precise_capturing<'tcx>(                 concatenated_bounds,             });         } else {-            let mut next_fresh_param = || {-                ['T', 'U', 'V', 'W', 'X', 'Y', 'A', 'B', 'C']-                    .into_iter()-                    .map(sym::character)-                    .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}"))+            let names_lifetimes = named_suggs.is_some();+            if let Some(named) = named_suggs {+                suggs.extend(named);             } else {-                (generics.span, format!("<{new_params}>"))-            });+                captured_lifetimes.insert(new_lifetime); -            let concatenated_bounds = captured_lifetimes-                .into_iter()-                .chain(captured_non_lifetimes)-                .map(|sym| sym.to_string())-                .collect::<Vec<_>>()-                .join(", ");+                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}>"),+                ));+            } -            suggs.push((-                tcx.def_span(opaque_def_id).shrink_to_hi(),-                format!(" + use<{concatenated_bounds}>"),-            ));+            // Add generics or concatenate to the end of the list.+            if let Some(params_span) = generics.span_for_param_suggestion() {+                suggs.push((params_span, format!(", {new_params}")));+            } else if let Some((_, new_lifetimes)) =+                suggs.iter_mut().find(|(span, _)| *span == generics.span)+            {+                // `suggest_name_regions` already pushed `<'a, 'b>` at `generics.span`, so a second+                // part at the same empty span cannot apply. the type params are spliced into the+                // existing string, with lifetimes first because the grammar requires it.+                *new_lifetimes = format!("{}, {new_params}>", new_lifetimes.trim_end_matches('>'));+            } else {+                suggs.push((generics.span, format!("<{new_params}>")));+            } -            diag.subdiagnostic(diagnostics::AddPreciseCapturingAndParams {-                suggs,-                new_lifetime,-                apit_spans,-            });+            if names_lifetimes {+                diag.subdiagnostic(diagnostics::AddPreciseCapturingNameLifetimesAndParams {+                    suggs,+                    apit_spans,+                });+            } else {+                diag.subdiagnostic(diagnostics::AddPreciseCapturingAndParams {+                    suggs,+                    new_lifetime,+                    apit_spans,+                });+            }         }     } }
tests/ui/impl-trait/precise-capturing/name-anon-lifetime-suggestion-apit-bound.rsadded15 + / 0 −
@@ -0,0 +1,15 @@+//@ edition: 2021++// regression test: a lifetime inside an APIT bound would panic the suggestion emitter+// once naming was applied to APITs, because the replacement part overlapped the APIT span.+// the expected output is the '_ fallback, which is not a correct suggestion but avoids+// the panic. the sibling test covers the cases where naming succeeds.++#![feature(anonymous_lifetime_in_impl_trait)]++fn anon_in_apit_bound(x: &(), y: &(), mut z: impl Iterator<Item = &u8>) -> impl Sized {+    (x, y, z.next())+    //~^ ERROR hidden type for `impl Sized` captures lifetime that does not appear in bounds+}++fn main() {}
tests/ui/impl-trait/precise-capturing/name-anon-lifetime-suggestion-apit-bound.stderradded24 + / 0 −
@@ -0,0 +1,24 @@+error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds+  --> $DIR/name-anon-lifetime-suggestion-apit-bound.rs:11:5+   |+LL | fn anon_in_apit_bound(x: &(), y: &(), mut z: impl Iterator<Item = &u8>) -> impl Sized {+   |                          ---                                               ---------- opaque type defined here+   |                          |+   |                          hidden type `(&(), &(), Option<&u8>)` captures the anonymous lifetime defined here+LL |     (x, y, z.next())+   |     ^^^^^^^^^^^^^^^^+   |+note: you could use a `use<...>` bound to explicitly capture `'_`, but argument-position `impl Trait`s are not nameable+  --> $DIR/name-anon-lifetime-suggestion-apit-bound.rs:10:46+   |+LL | fn anon_in_apit_bound(x: &(), y: &(), mut z: impl Iterator<Item = &u8>) -> 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 anon_in_apit_bound(x: &(), y: &(), mut z: impl Iterator<Item = &u8>) -> impl Sized {+LL + fn anon_in_apit_bound<T: Iterator<Item = &u8>>(x: &(), y: &(), mut z: T) -> impl Sized + use<'_, T> {+   |++error: aborting due to 1 previous error++For more information about this error, try `rustc --explain E0700`.
tests/ui/impl-trait/precise-capturing/name-anon-lifetime-suggestion-apit.fixedadded31 + / 0 −
@@ -0,0 +1,31 @@+//@ edition: 2021+//@ run-rustfix++#![allow(dead_code)]++fn no_generics<'a, 'b, T: Sized>(x: &'a (), y: &'b (), _z: T) -> impl Sized + use<'a, 'b, T> {+    (x, y)+    //~^ ERROR hidden type for `impl Sized` captures lifetime that does not appear in bounds+}++fn with_type_param<'a, 'b, U, T: Sized>(x: &'a (), y: &'b (), _z: T, _u: U) -> impl Sized + use<'a, 'b, U, T> {+    (x, y)+    //~^ ERROR hidden type for `impl Sized` captures lifetime that does not appear in bounds+}++fn with_lifetime_param<'b, 'c, 'a, T: Sized>(x: &'a (), y: &'b (), z: &'c (), _w: T) -> impl Sized + use<'a, 'b, 'c, T> {+    (x, y, z)+    //~^ ERROR hidden type for `impl Sized` captures lifetime that does not appear in bounds+}++fn two_apits<'a, 'b, T: Sized, U: Copy>(x: &'a (), y: &'b (), _a: T, _b: U) -> impl Sized + use<'a, 'b, T, U> {+    (x, y)+    //~^ ERROR hidden type for `impl Sized` captures lifetime that does not appear in bounds+}++fn one_anon<T: Sized>(x: &(), _z: T) -> impl Sized + use<'_, T> {+    x+    //~^ ERROR hidden type for `impl Sized` captures lifetime that does not appear in bounds+}++fn main() {}
tests/ui/impl-trait/precise-capturing/name-anon-lifetime-suggestion-apit.rsadded31 + / 0 −
@@ -0,0 +1,31 @@+//@ edition: 2021+//@ run-rustfix++#![allow(dead_code)]++fn no_generics(x: &(), y: &(), _z: impl Sized) -> impl Sized {+    (x, y)+    //~^ ERROR hidden type for `impl Sized` captures lifetime that does not appear in bounds+}++fn with_type_param<U>(x: &(), y: &(), _z: impl Sized, _u: U) -> impl Sized {+    (x, y)+    //~^ ERROR hidden type for `impl Sized` captures lifetime that does not appear in bounds+}++fn with_lifetime_param<'a>(x: &'a (), y: &(), z: &(), _w: impl Sized) -> impl Sized {+    (x, y, z)+    //~^ ERROR hidden type for `impl Sized` captures lifetime that does not appear in bounds+}++fn two_apits(x: &(), y: &(), _a: impl Sized, _b: impl Copy) -> impl Sized {+    (x, y)+    //~^ ERROR hidden type for `impl Sized` captures lifetime that does not appear in bounds+}++fn one_anon(x: &(), _z: impl Sized) -> impl Sized {+    x+    //~^ ERROR hidden type for `impl Sized` captures lifetime that does not appear in bounds+}++fn main() {}
tests/ui/impl-trait/precise-capturing/name-anon-lifetime-suggestion-apit.stderradded108 + / 0 −
@@ -0,0 +1,108 @@+error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds+  --> $DIR/name-anon-lifetime-suggestion-apit.rs:7:5+   |+LL | fn no_generics(x: &(), y: &(), _z: impl Sized) -> impl Sized {+   |                   ---                             ---------- opaque type defined here+   |                   |+   |                   hidden type `(&(), &())` captures the anonymous lifetime defined here+LL |     (x, y)+   |     ^^^^^^+   |+note: argument-position `impl Trait` cannot be named in a `use<...>` bound and must be turned into type parameters+  --> $DIR/name-anon-lifetime-suggestion-apit.rs:6:36+   |+LL | fn no_generics(x: &(), y: &(), _z: impl Sized) -> impl Sized {+   |                                    ^^^^^^^^^^+help: add a `use<...>` bound and name the elided lifetimes after turning all argument-position `impl Trait` into type parameters, noting that this possibly affects the API of this crate+   |+LL - fn no_generics(x: &(), y: &(), _z: impl Sized) -> impl Sized {+LL + fn no_generics<'a, 'b, T: Sized>(x: &'a (), y: &'b (), _z: T) -> impl Sized + use<'a, 'b, T> {+   |++error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds+  --> $DIR/name-anon-lifetime-suggestion-apit.rs:12:5+   |+LL | fn with_type_param<U>(x: &(), y: &(), _z: impl Sized, _u: U) -> impl Sized {+   |                          ---                                    ---------- opaque type defined here+   |                          |+   |                          hidden type `(&(), &())` captures the anonymous lifetime defined here+LL |     (x, y)+   |     ^^^^^^+   |+note: argument-position `impl Trait` cannot be named in a `use<...>` bound and must be turned into type parameters+  --> $DIR/name-anon-lifetime-suggestion-apit.rs:11:43+   |+LL | fn with_type_param<U>(x: &(), y: &(), _z: impl Sized, _u: U) -> impl Sized {+   |                                           ^^^^^^^^^^+help: add a `use<...>` bound and name the elided lifetimes after turning all argument-position `impl Trait` into type parameters, noting that this possibly affects the API of this crate+   |+LL - fn with_type_param<U>(x: &(), y: &(), _z: impl Sized, _u: U) -> impl Sized {+LL + fn with_type_param<'a, 'b, U, T: Sized>(x: &'a (), y: &'b (), _z: T, _u: U) -> impl Sized + use<'a, 'b, U, T> {+   |++error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds+  --> $DIR/name-anon-lifetime-suggestion-apit.rs:17:5+   |+LL | fn with_lifetime_param<'a>(x: &'a (), y: &(), z: &(), _w: impl Sized) -> impl Sized {+   |                        --                                                ---------- opaque type defined here+   |                        |+   |                        hidden type `(&'a (), &(), &())` captures the lifetime `'a` as defined here+LL |     (x, y, z)+   |     ^^^^^^^^^+   |+note: argument-position `impl Trait` cannot be named in a `use<...>` bound and must be turned into type parameters+  --> $DIR/name-anon-lifetime-suggestion-apit.rs:16:59+   |+LL | fn with_lifetime_param<'a>(x: &'a (), y: &(), z: &(), _w: impl Sized) -> impl Sized {+   |                                                           ^^^^^^^^^^+help: add a `use<...>` bound and name the elided lifetimes after turning all argument-position `impl Trait` into type parameters, noting that this possibly affects the API of this crate+   |+LL - fn with_lifetime_param<'a>(x: &'a (), y: &(), z: &(), _w: impl Sized) -> impl Sized {+LL + fn with_lifetime_param<'b, 'c, 'a, T: Sized>(x: &'a (), y: &'b (), z: &'c (), _w: T) -> impl Sized + use<'a, 'b, 'c, T> {+   |++error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds+  --> $DIR/name-anon-lifetime-suggestion-apit.rs:22:5+   |+LL | fn two_apits(x: &(), y: &(), _a: impl Sized, _b: impl Copy) -> impl Sized {+   |                 ---                                            ---------- opaque type defined here+   |                 |+   |                 hidden type `(&(), &())` captures the anonymous lifetime defined here+LL |     (x, y)+   |     ^^^^^^+   |+note: argument-position `impl Trait` cannot be named in a `use<...>` bound and must be turned into type parameters+  --> $DIR/name-anon-lifetime-suggestion-apit.rs:21:34+   |+LL | fn two_apits(x: &(), y: &(), _a: impl Sized, _b: impl Copy) -> impl Sized {+   |                                  ^^^^^^^^^^      ^^^^^^^^^+help: add a `use<...>` bound and name the elided lifetimes after turning all argument-position `impl Trait` into type parameters, noting that this possibly affects the API of this crate+   |+LL - fn two_apits(x: &(), y: &(), _a: impl Sized, _b: impl Copy) -> impl Sized {+LL + fn two_apits<'a, 'b, T: Sized, U: Copy>(x: &'a (), y: &'b (), _a: T, _b: U) -> impl Sized + use<'a, 'b, T, U> {+   |++error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds+  --> $DIR/name-anon-lifetime-suggestion-apit.rs:27:5+   |+LL | fn one_anon(x: &(), _z: impl Sized) -> impl Sized {+   |                ---                     ---------- opaque type defined here+   |                |+   |                hidden type `&()` captures the anonymous lifetime defined here+LL |     x+   |     ^+   |+note: you could use a `use<...>` bound to explicitly capture `'_`, but argument-position `impl Trait`s are not nameable+  --> $DIR/name-anon-lifetime-suggestion-apit.rs:26:25+   |+LL | fn one_anon(x: &(), _z: impl Sized) -> 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 one_anon(x: &(), _z: impl Sized) -> impl Sized {+LL + fn one_anon<T: Sized>(x: &(), _z: T) -> impl Sized + use<'_, T> {+   |++error: aborting due to 5 previous errors++For more information about this error, try `rustc --explain E0700`.