about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-03-21 04:11:09 +0000
committerbors <bors@rust-lang.org>2024-03-21 04:11:09 +0000
commit6e1f7b538a1d6fc6208b1821139148a8d6dc6560 (patch)
tree8321b2c3fbce5a6ea56b60cab57e0fb91d8cd0c4
parent6a6cd6517dac28f9c3f0476e4ba436a2010e40d9 (diff)
parentc270a42fea7ac5897756a55a4797e06244cf947b (diff)
downloadrust-6e1f7b538a1d6fc6208b1821139148a8d6dc6560.tar.gz
rust-6e1f7b538a1d6fc6208b1821139148a8d6dc6560.zip
Auto merge of #121587 - ShoyuVanilla:fix-issue-121267, r=TaKO8Ki
Fix bad span for explicit lifetime suggestions

Fixes #121267

Current explicit lifetime suggestions are not showing correct spans for some lifetimes - e.g. elided lifetime generic parameters;

This should be done correctly regarding elided lifetime kind like the following code

https://github.com/rust-lang/rust/blob/43fdd4916d19f4004e23d422b5547637ad67ab21/compiler/rustc_resolve/src/late/diagnostics.rs#L3015-L3044
-rw-r--r--compiler/rustc_ast_lowering/src/lib.rs63
-rw-r--r--compiler/rustc_hir/src/def.rs2
-rw-r--r--compiler/rustc_hir/src/hir.rs16
-rw-r--r--compiler/rustc_hir_analysis/src/check/compare_impl_item.rs2
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs71
-rw-r--r--compiler/rustc_infer/src/lib.rs1
-rw-r--r--compiler/rustc_resolve/src/late.rs44
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs14
-rw-r--r--src/librustdoc/clean/mod.rs5
-rw-r--r--tests/ui/suggestions/lifetimes/explicit-lifetime-suggestion-in-proper-span-issue-121267.rs12
-rw-r--r--tests/ui/suggestions/lifetimes/explicit-lifetime-suggestion-in-proper-span-issue-121267.stderr20
11 files changed, 167 insertions, 83 deletions
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index 6a7ee936f66..d2744e8ff68 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -55,7 +55,9 @@ use rustc_errors::{DiagArgFromDisplay, DiagCtxt, StashKey};
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, LifetimeRes, Namespace, PartialRes, PerNS, Res};
 use rustc_hir::def_id::{LocalDefId, LocalDefIdMap, CRATE_DEF_ID, LOCAL_CRATE};
-use rustc_hir::{ConstArg, GenericArg, ItemLocalMap, ParamName, TraitCandidate};
+use rustc_hir::{
+    ConstArg, GenericArg, ItemLocalMap, MissingLifetimeKind, ParamName, TraitCandidate,
+};
 use rustc_index::{Idx, IndexSlice, IndexVec};
 use rustc_macros::extension;
 use rustc_middle::span_bug;
@@ -797,7 +799,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
             LifetimeRes::Param { .. } => {
                 (hir::ParamName::Plain(ident), hir::LifetimeParamKind::Explicit)
             }
-            LifetimeRes::Fresh { param, .. } => {
+            LifetimeRes::Fresh { param, kind, .. } => {
                 // Late resolution delegates to us the creation of the `LocalDefId`.
                 let _def_id = self.create_def(
                     self.current_hir_id_owner.def_id,
@@ -808,7 +810,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
                 );
                 debug!(?_def_id);
 
-                (hir::ParamName::Fresh, hir::LifetimeParamKind::Elided)
+                (hir::ParamName::Fresh, hir::LifetimeParamKind::Elided(kind))
             }
             LifetimeRes::Static | LifetimeRes::Error => return None,
             res => panic!(
@@ -1605,13 +1607,13 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
 
         for lifetime in captured_lifetimes_to_duplicate {
             let res = self.resolver.get_lifetime_res(lifetime.id).unwrap_or(LifetimeRes::Error);
-            let old_def_id = match res {
-                LifetimeRes::Param { param: old_def_id, binder: _ } => old_def_id,
+            let (old_def_id, missing_kind) = match res {
+                LifetimeRes::Param { param: old_def_id, binder: _ } => (old_def_id, None),
 
-                LifetimeRes::Fresh { param, binder: _ } => {
+                LifetimeRes::Fresh { param, kind, .. } => {
                     debug_assert_eq!(lifetime.ident.name, kw::UnderscoreLifetime);
                     if let Some(old_def_id) = self.orig_opt_local_def_id(param) {
-                        old_def_id
+                        (old_def_id, Some(kind))
                     } else {
                         self.dcx()
                             .span_delayed_bug(lifetime.ident.span, "no def-id for fresh lifetime");
@@ -1651,6 +1653,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
                     duplicated_lifetime_node_id,
                     duplicated_lifetime_def_id,
                     self.lower_ident(lifetime.ident),
+                    missing_kind,
                 ));
 
                 // Now make an arg that we can use for the generic params of the opaque tykind.
@@ -1668,27 +1671,33 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
             let bounds = this
                 .with_remapping(captured_to_synthesized_mapping, |this| lower_item_bounds(this));
 
-            let generic_params = this.arena.alloc_from_iter(
-                synthesized_lifetime_definitions.iter().map(|&(new_node_id, new_def_id, ident)| {
-                    let hir_id = this.lower_node_id(new_node_id);
-                    let (name, kind) = if ident.name == kw::UnderscoreLifetime {
-                        (hir::ParamName::Fresh, hir::LifetimeParamKind::Elided)
-                    } else {
-                        (hir::ParamName::Plain(ident), hir::LifetimeParamKind::Explicit)
-                    };
+            let generic_params =
+                this.arena.alloc_from_iter(synthesized_lifetime_definitions.iter().map(
+                    |&(new_node_id, new_def_id, ident, missing_kind)| {
+                        let hir_id = this.lower_node_id(new_node_id);
+                        let (name, kind) = if ident.name == kw::UnderscoreLifetime {
+                            (
+                                hir::ParamName::Fresh,
+                                hir::LifetimeParamKind::Elided(
+                                    missing_kind.unwrap_or(MissingLifetimeKind::Underscore),
+                                ),
+                            )
+                        } else {
+                            (hir::ParamName::Plain(ident), hir::LifetimeParamKind::Explicit)
+                        };
 
-                    hir::GenericParam {
-                        hir_id,
-                        def_id: new_def_id,
-                        name,
-                        span: ident.span,
-                        pure_wrt_drop: false,
-                        kind: hir::GenericParamKind::Lifetime { kind },
-                        colon_span: None,
-                        source: hir::GenericParamSource::Generics,
-                    }
-                }),
-            );
+                        hir::GenericParam {
+                            hir_id,
+                            def_id: new_def_id,
+                            name,
+                            span: ident.span,
+                            pure_wrt_drop: false,
+                            kind: hir::GenericParamKind::Lifetime { kind },
+                            colon_span: None,
+                            source: hir::GenericParamSource::Generics,
+                        }
+                    },
+                ));
             debug!("lower_async_fn_ret_ty: generic_params={:#?}", generic_params);
 
             let lifetime_mapping = self.arena.alloc_slice(&synthesized_lifetime_args);
diff --git a/compiler/rustc_hir/src/def.rs b/compiler/rustc_hir/src/def.rs
index 1810193c16b..e8cecb1930f 100644
--- a/compiler/rustc_hir/src/def.rs
+++ b/compiler/rustc_hir/src/def.rs
@@ -810,6 +810,8 @@ pub enum LifetimeRes {
         param: NodeId,
         /// Id of the introducing place. See `Param`.
         binder: NodeId,
+        /// Kind of elided lifetime
+        kind: hir::MissingLifetimeKind,
     },
     /// This variant is used for anonymous lifetimes that we did not resolve during
     /// late resolution. Those lifetimes will be inferred by typechecking.
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index d0446785e4e..141b8a41801 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -456,6 +456,18 @@ impl GenericBound<'_> {
 
 pub type GenericBounds<'hir> = &'hir [GenericBound<'hir>];
 
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, HashStable_Generic, Debug)]
+pub enum MissingLifetimeKind {
+    /// An explicit `'_`.
+    Underscore,
+    /// An elided lifetime `&' ty`.
+    Ampersand,
+    /// An elided lifetime in brackets with written brackets.
+    Comma,
+    /// An elided lifetime with elided brackets.
+    Brackets,
+}
+
 #[derive(Copy, Clone, Debug, HashStable_Generic)]
 pub enum LifetimeParamKind {
     // Indicates that the lifetime definition was explicitly declared (e.g., in
@@ -464,7 +476,7 @@ pub enum LifetimeParamKind {
 
     // Indication that the lifetime was elided (e.g., in both cases in
     // `fn foo(x: &u8) -> &'_ u8 { x }`).
-    Elided,
+    Elided(MissingLifetimeKind),
 
     // Indication that the lifetime name was somehow in error.
     Error,
@@ -512,7 +524,7 @@ impl<'hir> GenericParam<'hir> {
     ///
     /// See `lifetime_to_generic_param` in `rustc_ast_lowering` for more information.
     pub fn is_elided_lifetime(&self) -> bool {
-        matches!(self.kind, GenericParamKind::Lifetime { kind: LifetimeParamKind::Elided })
+        matches!(self.kind, GenericParamKind::Lifetime { kind: LifetimeParamKind::Elided(_) })
     }
 }
 
diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs
index ec9e928ce59..d2fdff6177e 100644
--- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs
+++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs
@@ -1305,7 +1305,7 @@ fn compare_number_of_generics<'tcx>(
                     .iter()
                     .filter(|p| match p.kind {
                         hir::GenericParamKind::Lifetime {
-                            kind: hir::LifetimeParamKind::Elided,
+                            kind: hir::LifetimeParamKind::Elided(_),
                         } => {
                             // A fn can have an arbitrary number of extra elided lifetimes for the
                             // same signature.
diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs
index 503645191aa..fe70b631cdb 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs
@@ -13,8 +13,8 @@ use rustc_errors::{Applicability, Diag, ErrorGuaranteed, MultiSpan, Subdiagnosti
 use rustc_hir::def_id::DefId;
 use rustc_hir::intravisit::{walk_ty, Visitor};
 use rustc_hir::{
-    self as hir, GenericBound, GenericParamKind, Item, ItemKind, Lifetime, LifetimeName, Node,
-    TyKind,
+    self as hir, GenericBound, GenericParam, GenericParamKind, Item, ItemKind, Lifetime,
+    LifetimeName, LifetimeParamKind, MissingLifetimeKind, Node, TyKind,
 };
 use rustc_middle::ty::{
     self, AssocItemContainer, StaticLifetimeVisitor, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor,
@@ -355,20 +355,8 @@ pub fn suggest_new_region_bound(
                     // introducing a new lifetime `'a` or making use of one from existing named lifetimes if any
                     if let Some(id) = scope_def_id
                         && let Some(generics) = tcx.hir().get_generics(id)
-                        && let mut spans_suggs = generics
-                            .params
-                            .iter()
-                            .filter(|p| p.is_elided_lifetime())
-                            .map(|p| {
-                                if p.span.hi() - p.span.lo() == rustc_span::BytePos(1) {
-                                    // Ampersand (elided without '_)
-                                    (p.span.shrink_to_hi(), format!("{name} "))
-                                } else {
-                                    // Underscore (elided with '_)
-                                    (p.span, name.to_string())
-                                }
-                            })
-                            .collect::<Vec<_>>()
+                        && let mut spans_suggs =
+                            make_elided_region_spans_suggs(name, generics.params.iter())
                         && spans_suggs.len() > 1
                     {
                         let use_lt = if existing_lt_name == None {
@@ -430,6 +418,57 @@ pub fn suggest_new_region_bound(
     }
 }
 
+fn make_elided_region_spans_suggs<'a>(
+    name: &str,
+    generic_params: impl Iterator<Item = &'a GenericParam<'a>>,
+) -> Vec<(Span, String)> {
+    let mut spans_suggs = Vec::new();
+    let mut bracket_span = None;
+    let mut consecutive_brackets = 0;
+
+    let mut process_consecutive_brackets =
+        |span: Option<Span>, spans_suggs: &mut Vec<(Span, String)>| {
+            if span
+                .is_some_and(|span| bracket_span.map_or(true, |bracket_span| span == bracket_span))
+            {
+                consecutive_brackets += 1;
+            } else if let Some(bracket_span) = bracket_span.take() {
+                let sugg = std::iter::once("<")
+                    .chain(std::iter::repeat(name).take(consecutive_brackets).intersperse(", "))
+                    .chain([">"])
+                    .collect();
+                spans_suggs.push((bracket_span.shrink_to_hi(), sugg));
+                consecutive_brackets = 0;
+            }
+            bracket_span = span;
+        };
+
+    for p in generic_params {
+        if let GenericParamKind::Lifetime { kind: LifetimeParamKind::Elided(kind) } = p.kind {
+            match kind {
+                MissingLifetimeKind::Underscore => {
+                    process_consecutive_brackets(None, &mut spans_suggs);
+                    spans_suggs.push((p.span, name.to_string()))
+                }
+                MissingLifetimeKind::Ampersand => {
+                    process_consecutive_brackets(None, &mut spans_suggs);
+                    spans_suggs.push((p.span.shrink_to_hi(), format!("{name} ")));
+                }
+                MissingLifetimeKind::Comma => {
+                    process_consecutive_brackets(None, &mut spans_suggs);
+                    spans_suggs.push((p.span.shrink_to_hi(), format!("{name}, ")));
+                }
+                MissingLifetimeKind::Brackets => {
+                    process_consecutive_brackets(Some(p.span), &mut spans_suggs);
+                }
+            }
+        }
+    }
+    process_consecutive_brackets(None, &mut spans_suggs);
+
+    spans_suggs
+}
+
 impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
     pub fn get_impl_ident_and_self_ty_from_trait(
         tcx: TyCtxt<'tcx>,
diff --git a/compiler/rustc_infer/src/lib.rs b/compiler/rustc_infer/src/lib.rs
index 807d5500a90..ee9ce842d00 100644
--- a/compiler/rustc_infer/src/lib.rs
+++ b/compiler/rustc_infer/src/lib.rs
@@ -24,6 +24,7 @@
 #![feature(extend_one)]
 #![feature(let_chains)]
 #![feature(if_let_guard)]
+#![feature(iter_intersperse)]
 #![feature(iterator_try_collect)]
 #![feature(try_blocks)]
 #![feature(yeet_expr)]
diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs
index fc037404829..b2b339d2521 100644
--- a/compiler/rustc_resolve/src/late.rs
+++ b/compiler/rustc_resolve/src/late.rs
@@ -22,7 +22,7 @@ use rustc_errors::{
 use rustc_hir::def::Namespace::{self, *};
 use rustc_hir::def::{self, CtorKind, DefKind, LifetimeRes, NonMacroAttrKind, PartialRes, PerNS};
 use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID, LOCAL_CRATE};
-use rustc_hir::{PrimTy, TraitCandidate};
+use rustc_hir::{MissingLifetimeKind, PrimTy, TraitCandidate};
 use rustc_middle::middle::resolve_bound_vars::Set1;
 use rustc_middle::{bug, span_bug};
 use rustc_session::config::{CrateType, ResolveDocLinks};
@@ -44,9 +44,7 @@ type Res = def::Res<NodeId>;
 
 type IdentMap<T> = FxHashMap<Ident, T>;
 
-use diagnostics::{
-    ElisionFnParameter, LifetimeElisionCandidate, MissingLifetime, MissingLifetimeKind,
-};
+use diagnostics::{ElisionFnParameter, LifetimeElisionCandidate, MissingLifetime};
 
 #[derive(Copy, Clone, Debug)]
 struct BindingInfo {
@@ -1637,22 +1635,16 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
     fn resolve_anonymous_lifetime(&mut self, lifetime: &Lifetime, elided: bool) {
         debug_assert_eq!(lifetime.ident.name, kw::UnderscoreLifetime);
 
-        let missing_lifetime = MissingLifetime {
-            id: lifetime.id,
-            span: lifetime.ident.span,
-            kind: if elided {
-                MissingLifetimeKind::Ampersand
-            } else {
-                MissingLifetimeKind::Underscore
-            },
-            count: 1,
-        };
+        let kind =
+            if elided { MissingLifetimeKind::Ampersand } else { MissingLifetimeKind::Underscore };
+        let missing_lifetime =
+            MissingLifetime { id: lifetime.id, span: lifetime.ident.span, kind, count: 1 };
         let elision_candidate = LifetimeElisionCandidate::Missing(missing_lifetime);
         for (i, rib) in self.lifetime_ribs.iter().enumerate().rev() {
             debug!(?rib.kind);
             match rib.kind {
                 LifetimeRibKind::AnonymousCreateParameter { binder, .. } => {
-                    let res = self.create_fresh_lifetime(lifetime.ident, binder);
+                    let res = self.create_fresh_lifetime(lifetime.ident, binder, kind);
                     self.record_lifetime_res(lifetime.id, res, elision_candidate);
                     return;
                 }
@@ -1744,13 +1736,18 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
     }
 
     #[instrument(level = "debug", skip(self))]
-    fn create_fresh_lifetime(&mut self, ident: Ident, binder: NodeId) -> LifetimeRes {
+    fn create_fresh_lifetime(
+        &mut self,
+        ident: Ident,
+        binder: NodeId,
+        kind: MissingLifetimeKind,
+    ) -> LifetimeRes {
         debug_assert_eq!(ident.name, kw::UnderscoreLifetime);
         debug!(?ident.span);
 
         // Leave the responsibility to create the `LocalDefId` to lowering.
         let param = self.r.next_node_id();
-        let res = LifetimeRes::Fresh { param, binder };
+        let res = LifetimeRes::Fresh { param, binder, kind };
         self.record_lifetime_param(param, res);
 
         // Record the created lifetime parameter so lowering can pick it up and add it to HIR.
@@ -1844,14 +1841,15 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
             };
             let ident = Ident::new(kw::UnderscoreLifetime, elided_lifetime_span);
 
+            let kind = if segment.has_generic_args {
+                MissingLifetimeKind::Comma
+            } else {
+                MissingLifetimeKind::Brackets
+            };
             let missing_lifetime = MissingLifetime {
                 id: node_ids.start,
                 span: elided_lifetime_span,
-                kind: if segment.has_generic_args {
-                    MissingLifetimeKind::Comma
-                } else {
-                    MissingLifetimeKind::Brackets
-                },
+                kind,
                 count: expected_lifetimes,
             };
             let mut should_lint = true;
@@ -1897,7 +1895,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
                         // Group all suggestions into the first record.
                         let mut candidate = LifetimeElisionCandidate::Missing(missing_lifetime);
                         for id in node_ids {
-                            let res = self.create_fresh_lifetime(ident, binder);
+                            let res = self.create_fresh_lifetime(ident, binder, kind);
                             self.record_lifetime_res(
                                 id,
                                 res,
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index 47f8cc56139..1b8f2fc005c 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -24,7 +24,7 @@ use rustc_errors::{
 use rustc_hir as hir;
 use rustc_hir::def::{self, CtorKind, CtorOf, DefKind};
 use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
-use rustc_hir::PrimTy;
+use rustc_hir::{MissingLifetimeKind, PrimTy};
 use rustc_session::lint;
 use rustc_session::Session;
 use rustc_span::edit_distance::find_best_match_for_name;
@@ -109,18 +109,6 @@ pub(super) struct MissingLifetime {
     pub count: usize,
 }
 
-#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
-pub(super) enum MissingLifetimeKind {
-    /// An explicit `'_`.
-    Underscore,
-    /// An elided lifetime `&' ty`.
-    Ampersand,
-    /// An elided lifetime in brackets with written brackets.
-    Comma,
-    /// An elided lifetime with elided brackets.
-    Brackets,
-}
-
 /// Description of the lifetimes appearing in a function parameter.
 /// This is used to provide a literal explanation to the elision failure.
 #[derive(Clone, Debug)]
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 855fb132fc8..810a870c35c 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -626,7 +626,10 @@ fn is_impl_trait(param: &hir::GenericParam<'_>) -> bool {
 ///
 /// See `lifetime_to_generic_param` in `rustc_ast_lowering` for more information.
 fn is_elided_lifetime(param: &hir::GenericParam<'_>) -> bool {
-    matches!(param.kind, hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Elided })
+    matches!(
+        param.kind,
+        hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Elided(_) }
+    )
 }
 
 pub(crate) fn clean_generics<'tcx>(
diff --git a/tests/ui/suggestions/lifetimes/explicit-lifetime-suggestion-in-proper-span-issue-121267.rs b/tests/ui/suggestions/lifetimes/explicit-lifetime-suggestion-in-proper-span-issue-121267.rs
new file mode 100644
index 00000000000..34afd363129
--- /dev/null
+++ b/tests/ui/suggestions/lifetimes/explicit-lifetime-suggestion-in-proper-span-issue-121267.rs
@@ -0,0 +1,12 @@
+fn main() {}
+
+fn foo(_src: &crate::Foo) -> Option<i32> {
+    todo!()
+}
+fn bar(src: &crate::Foo) -> impl Iterator<Item = i32> {
+    [0].into_iter()
+ //~^ ERROR hidden type for `impl Iterator<Item = i32>` captures lifetime that does not appear in bounds
+        .filter_map(|_| foo(src))
+}
+
+struct Foo<'a>(&'a str);
diff --git a/tests/ui/suggestions/lifetimes/explicit-lifetime-suggestion-in-proper-span-issue-121267.stderr b/tests/ui/suggestions/lifetimes/explicit-lifetime-suggestion-in-proper-span-issue-121267.stderr
new file mode 100644
index 00000000000..aeeec3aca34
--- /dev/null
+++ b/tests/ui/suggestions/lifetimes/explicit-lifetime-suggestion-in-proper-span-issue-121267.stderr
@@ -0,0 +1,20 @@
+error[E0700]: hidden type for `impl Iterator<Item = i32>` captures lifetime that does not appear in bounds
+  --> $DIR/explicit-lifetime-suggestion-in-proper-span-issue-121267.rs:7:5
+   |
+LL |   fn bar(src: &crate::Foo) -> impl Iterator<Item = i32> {
+   |                ----------     ------------------------- opaque type defined here
+   |                |
+   |                hidden type `FilterMap<std::slice::Iter<'static, i32>, {closure@$DIR/explicit-lifetime-suggestion-in-proper-span-issue-121267.rs:9:21: 9:24}>` captures the anonymous lifetime defined here
+LL | /     [0].into_iter()
+LL | |
+LL | |         .filter_map(|_| foo(src))
+   | |_________________________________^
+   |
+help: to declare that `impl Iterator<Item = i32>` captures `'_`, you can introduce a named lifetime parameter `'a`
+   |
+LL | fn bar<'a>(src: &'a crate::Foo<'a>) -> impl Iterator<Item = i32> + 'a  {
+   |       ++++       ++           ++++                               ++++
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0700`.